Implemented support for handling multiple playlists in tests (#657)
Added support for handling multiple playlists in the Mock TCP server. Refactored SlideUpPanelTests to support the new playlist handler. Some code cleanup and enhancements - Removed throw exception declarations from methods that never throw the exception. - Changed deleting the test database on the test device to delete all databases. The former method of only deleting the test database resulted in many old databases that were not removed when the test run crashed. - Enhanced robustness of testing async code by explicitly waiting for a view to reach a certain state.
This commit is contained in:
parent
1020b8b7de
commit
3b2447607b
|
@ -177,10 +177,26 @@ public class EspressoTestUtils {
|
||||||
* Checks that the list contains item(s) matching search query
|
* Checks that the list contains item(s) matching search query
|
||||||
* @param query text each element must contain
|
* @param query text each element must contain
|
||||||
* @param listSize amount of elements expected in list
|
* @param listSize amount of elements expected in list
|
||||||
|
* @param resourceId resource identifier or list view
|
||||||
*/
|
*/
|
||||||
public static void checkListMatchesSearchQuery(String query, int listSize, int resourceId) {
|
public static void checkListMatchesSearchQuery(String query, int listSize, int resourceId) {
|
||||||
|
onView(isRoot()).perform(ViewActions.waitForView(resourceId, new ViewActions.CheckStatus() {
|
||||||
|
@Override
|
||||||
|
public boolean check(View v) {
|
||||||
|
return v.isShown();
|
||||||
|
}
|
||||||
|
}, 10000));
|
||||||
|
|
||||||
onView(allOf(withId(resourceId), isDisplayed()))
|
onView(allOf(withId(resourceId), isDisplayed()))
|
||||||
.check(matches(Matchers.withOnlyMatchingDataItems(hasDescendant(withText(containsString(query))))));
|
.check(matches(Matchers.withOnlyMatchingDataItems(hasDescendant(withText(containsString(query))))));
|
||||||
|
checkRecyclerViewListsize(listSize, resourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the list size matches the given list size
|
||||||
|
* @param listSize amount of elements expected in list
|
||||||
|
*/
|
||||||
|
public static void checkRecyclerViewListsize(int listSize, int resourceId) {
|
||||||
onView(allOf(withId(resourceId), isDisplayed()))
|
onView(allOf(withId(resourceId), isDisplayed()))
|
||||||
.check(matches(Matchers.withRecyclerViewSize(listSize)));
|
.check(matches(Matchers.withRecyclerViewSize(listSize)));
|
||||||
}
|
}
|
||||||
|
@ -294,7 +310,7 @@ public class EspressoTestUtils {
|
||||||
*/
|
*/
|
||||||
public static void selectListItemRotateDeviceAndCheckActionbarTitle(String itemText,
|
public static void selectListItemRotateDeviceAndCheckActionbarTitle(String itemText,
|
||||||
int listResourceId,
|
int listResourceId,
|
||||||
String actionbarTitle,
|
final String actionbarTitle,
|
||||||
Activity activity) {
|
Activity activity) {
|
||||||
EspressoTestUtils.clickRecyclerViewItem(itemText, listResourceId);
|
EspressoTestUtils.clickRecyclerViewItem(itemText, listResourceId);
|
||||||
EspressoTestUtils.rotateDevice(activity);
|
EspressoTestUtils.rotateDevice(activity);
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.xbmc.kore.testutils.tcpserver.handlers.InputHandler;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.JSONConnectionHandlerManager;
|
import org.xbmc.kore.testutils.tcpserver.handlers.JSONConnectionHandlerManager;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.JSONRPCHandler;
|
import org.xbmc.kore.testutils.tcpserver.handlers.JSONRPCHandler;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler;
|
import org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler;
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.PlaylistHandler;
|
||||||
import org.xbmc.kore.ui.sections.hosts.HostFragmentManualConfiguration;
|
import org.xbmc.kore.ui.sections.hosts.HostFragmentManualConfiguration;
|
||||||
import org.xbmc.kore.utils.LogUtils;
|
import org.xbmc.kore.utils.LogUtils;
|
||||||
|
|
||||||
|
@ -69,6 +70,7 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
|
||||||
private static PlayerHandler playerHandler;
|
private static PlayerHandler playerHandler;
|
||||||
private static ApplicationHandler applicationHandler;
|
private static ApplicationHandler applicationHandler;
|
||||||
private static InputHandler inputHandler;
|
private static InputHandler inputHandler;
|
||||||
|
private static PlaylistHandler playlistHandler;
|
||||||
private int kodiMajorVersion = HostInfo.DEFAULT_KODI_VERSION_MAJOR;
|
private int kodiMajorVersion = HostInfo.DEFAULT_KODI_VERSION_MAJOR;
|
||||||
private HostInfo hostInfo;
|
private HostInfo hostInfo;
|
||||||
|
|
||||||
|
@ -77,11 +79,13 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
|
||||||
playerHandler = new PlayerHandler();
|
playerHandler = new PlayerHandler();
|
||||||
applicationHandler = new ApplicationHandler();
|
applicationHandler = new ApplicationHandler();
|
||||||
inputHandler = new InputHandler();
|
inputHandler = new InputHandler();
|
||||||
|
playlistHandler = new PlaylistHandler();
|
||||||
manager = new JSONConnectionHandlerManager();
|
manager = new JSONConnectionHandlerManager();
|
||||||
manager.addHandler(playerHandler);
|
manager.addHandler(playerHandler);
|
||||||
manager.addHandler(applicationHandler);
|
manager.addHandler(applicationHandler);
|
||||||
manager.addHandler(inputHandler);
|
manager.addHandler(inputHandler);
|
||||||
manager.addHandler(new AddonsHandler());
|
manager.addHandler(new AddonsHandler());
|
||||||
|
manager.addHandler(playlistHandler);
|
||||||
manager.addHandler(new JSONRPCHandler());
|
manager.addHandler(new JSONRPCHandler());
|
||||||
server = new MockTcpServer(manager);
|
server = new MockTcpServer(manager);
|
||||||
server.start();
|
server.start();
|
||||||
|
@ -134,7 +138,7 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
|
||||||
playerHandler.reset();
|
playerHandler.reset();
|
||||||
|
|
||||||
Context context = activityTestRule.getActivity();
|
Context context = activityTestRule.getActivity();
|
||||||
Database.flush(context.getContentResolver(), hostInfo);
|
Database.flush(context.getContentResolver());
|
||||||
Utils.enableAnimations(context);
|
Utils.enableAnimations(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +165,10 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
|
||||||
this.kodiMajorVersion = kodiMajorVersion;
|
this.kodiMajorVersion = kodiMajorVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JSONConnectionHandlerManager getConnectionHandlerManager() {
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
public static PlayerHandler getPlayerHandler() {
|
public static PlayerHandler getPlayerHandler() {
|
||||||
return playerHandler;
|
return playerHandler;
|
||||||
}
|
}
|
||||||
|
@ -172,4 +180,8 @@ abstract public class AbstractTestClass<T extends AppCompatActivity> {
|
||||||
public static InputHandler getInputHandler() {
|
public static InputHandler getInputHandler() {
|
||||||
return inputHandler;
|
return inputHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PlaylistHandler getPlaylistHandler() {
|
||||||
|
return playlistHandler;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.xbmc.kore.R;
|
import org.xbmc.kore.R;
|
||||||
import org.xbmc.kore.host.HostInfo;
|
|
||||||
import org.xbmc.kore.testhelpers.EspressoTestUtils;
|
import org.xbmc.kore.testhelpers.EspressoTestUtils;
|
||||||
import org.xbmc.kore.testhelpers.action.ViewActions;
|
import org.xbmc.kore.testhelpers.action.ViewActions;
|
||||||
import org.xbmc.kore.tests.ui.AbstractTestClass;
|
import org.xbmc.kore.tests.ui.AbstractTestClass;
|
||||||
|
@ -65,7 +64,6 @@ import static org.xbmc.kore.testhelpers.EspressoTestUtils.selectListItemPressBac
|
||||||
* {@link org.xbmc.kore.ui.sections.addon.AddonsActivity} to become idle.
|
* {@link org.xbmc.kore.ui.sections.addon.AddonsActivity} to become idle.
|
||||||
*/
|
*/
|
||||||
public class AddonsActivityTests extends AbstractTestClass<AddonsActivity> {
|
public class AddonsActivityTests extends AbstractTestClass<AddonsActivity> {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ActivityTestRule<AddonsActivity> mActivityRule = new ActivityTestRule<>(AddonsActivity.class);
|
public ActivityTestRule<AddonsActivity> mActivityRule = new ActivityTestRule<>(AddonsActivity.class);
|
||||||
|
|
||||||
|
|
|
@ -36,11 +36,15 @@ import org.xbmc.kore.testhelpers.Utils;
|
||||||
import org.xbmc.kore.testhelpers.action.ViewActions;
|
import org.xbmc.kore.testhelpers.action.ViewActions;
|
||||||
import org.xbmc.kore.tests.ui.AbstractTestClass;
|
import org.xbmc.kore.tests.ui.AbstractTestClass;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler;
|
import org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler;
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Application;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Player;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Player;
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Playlist;
|
||||||
import org.xbmc.kore.ui.sections.audio.MusicActivity;
|
import org.xbmc.kore.ui.sections.audio.MusicActivity;
|
||||||
import org.xbmc.kore.ui.widgets.HighlightButton;
|
import org.xbmc.kore.ui.widgets.HighlightButton;
|
||||||
import org.xbmc.kore.ui.widgets.RepeatModeButton;
|
import org.xbmc.kore.ui.widgets.RepeatModeButton;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import static android.support.test.espresso.Espresso.onView;
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
import static android.support.test.espresso.Espresso.pressBack;
|
import static android.support.test.espresso.Espresso.pressBack;
|
||||||
import static android.support.test.espresso.action.ViewActions.click;
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
|
@ -50,13 +54,17 @@ import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.xbmc.kore.testhelpers.EspressoTestUtils.clickAdapterViewItem;
|
import static org.xbmc.kore.testhelpers.EspressoTestUtils.clickAdapterViewItem;
|
||||||
import static org.xbmc.kore.testhelpers.EspressoTestUtils.rotateDevice;
|
import static org.xbmc.kore.testhelpers.EspressoTestUtils.rotateDevice;
|
||||||
import static org.xbmc.kore.testhelpers.EspressoTestUtils.waitForPanelState;
|
import static org.xbmc.kore.testhelpers.EspressoTestUtils.waitForPanelState;
|
||||||
import static org.xbmc.kore.testhelpers.Matchers.withHighlightState;
|
import static org.xbmc.kore.testhelpers.Matchers.withHighlightState;
|
||||||
import static org.xbmc.kore.testhelpers.Matchers.withProgress;
|
import static org.xbmc.kore.testhelpers.Matchers.withProgress;
|
||||||
|
import static org.xbmc.kore.testutils.TestUtils.createMusicItem;
|
||||||
|
import static org.xbmc.kore.testutils.TestUtils.createMusicVideoItem;
|
||||||
|
import static org.xbmc.kore.testutils.TestUtils.createVideoItem;
|
||||||
|
|
||||||
public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
|
|
||||||
|
@ -78,8 +86,14 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
public void setUp() throws Throwable {
|
public void setUp() throws Throwable {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
|
getPlaylistHandler().reset();
|
||||||
|
getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.AUDIO, createMusicItem(0, 0));
|
||||||
|
getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.VIDEO, createVideoItem(0, 1));
|
||||||
|
getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.VIDEO, createMusicVideoItem(0, 2));
|
||||||
|
|
||||||
getPlayerHandler().reset();
|
getPlayerHandler().reset();
|
||||||
getPlayerHandler().startPlay();
|
getPlayerHandler().setPlaylists(getPlaylistHandler().getPlaylists());
|
||||||
|
getPlayerHandler().startPlay(Playlist.playlistID.AUDIO, 0);
|
||||||
|
|
||||||
waitForPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
|
waitForPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
|
||||||
}
|
}
|
||||||
|
@ -120,8 +134,7 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void panelButtonsMoviesTest() {
|
public void panelButtonsMoviesTest() {
|
||||||
getPlayerHandler().setMediaType(PlayerHandler.TYPE.MOVIE);
|
getPlayerHandler().startPlay(Playlist.playlistID.VIDEO, 0);
|
||||||
getPlayerHandler().startPlay();
|
|
||||||
Player.GetItem item = getPlayerHandler().getMediaItem();
|
Player.GetItem item = getPlayerHandler().getMediaItem();
|
||||||
final String title = item.getTitle();
|
final String title = item.getTitle();
|
||||||
onView(isRoot()).perform(ViewActions.waitForView(
|
onView(isRoot()).perform(ViewActions.waitForView(
|
||||||
|
@ -146,8 +159,7 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void panelButtonsMusicVideoTest() {
|
public void panelButtonsMusicVideoTest() {
|
||||||
getPlayerHandler().setMediaType(PlayerHandler.TYPE.MUSICVIDEO);
|
getPlayerHandler().startPlay(Playlist.playlistID.VIDEO, 1);
|
||||||
getPlayerHandler().startPlay();
|
|
||||||
Player.GetItem item = getPlayerHandler().getMediaItem();
|
Player.GetItem item = getPlayerHandler().getMediaItem();
|
||||||
final String title = item.getTitle();
|
final String title = item.getTitle();
|
||||||
onView(isRoot()).perform(ViewActions.waitForView(
|
onView(isRoot()).perform(ViewActions.waitForView(
|
||||||
|
@ -374,7 +386,7 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
* 4. Result: Volume indicator should show volume level and server should be set to new volume level
|
* 4. Result: Volume indicator should show volume level and server should be set to new volume level
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void changeVolume() {
|
public void changeVolume() throws TimeoutException {
|
||||||
final int volume = 16;
|
final int volume = 16;
|
||||||
expandPanel();
|
expandPanel();
|
||||||
|
|
||||||
|
@ -382,7 +394,10 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
|
|
||||||
onView(withId(R.id.vli_seek_bar)).check(matches(withProgress(volume)));
|
onView(withId(R.id.vli_seek_bar)).check(matches(withProgress(volume)));
|
||||||
onView(withId(R.id.vli_volume_text)).check(matches(withText(String.valueOf(volume))));
|
onView(withId(R.id.vli_volume_text)).check(matches(withText(String.valueOf(volume))));
|
||||||
assertTrue(getApplicationHandler().getVolume() == volume);
|
|
||||||
|
getConnectionHandlerManager().waitForMethodHandled(Application.SetVolume.METHOD_NAME, 10000);
|
||||||
|
assertTrue("applicationHandler volume: "+ getApplicationHandler().getVolume()
|
||||||
|
+ " != " + volume, getApplicationHandler().getVolume() == volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -396,13 +411,15 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
* 4. Result: Volume indicator should show volume level and server should be set to new volume level
|
* 4. Result: Volume indicator should show volume level and server should be set to new volume level
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void restoreVolumeIndicatorOnRotate() {
|
public void restoreVolumeIndicatorOnRotate() throws TimeoutException {
|
||||||
final int volume = 16;
|
final int volume = 16;
|
||||||
expandPanel();
|
expandPanel();
|
||||||
onView(withId(R.id.vli_seek_bar)).perform(ViewActions.slideSeekBar(volume));
|
onView(withId(R.id.vli_seek_bar)).perform(ViewActions.slideSeekBar(volume));
|
||||||
|
|
||||||
rotateDevice(getActivity());
|
rotateDevice(getActivity());
|
||||||
|
|
||||||
|
assertTrue("applicationHandler volume: "+ getApplicationHandler().getVolume()
|
||||||
|
+ " != " + volume, getApplicationHandler().getVolume() == volume);
|
||||||
onView(isRoot()).perform(ViewActions.waitForView(R.id.vli_seek_bar, new ViewActions.CheckStatus() {
|
onView(isRoot()).perform(ViewActions.waitForView(R.id.vli_seek_bar, new ViewActions.CheckStatus() {
|
||||||
@Override
|
@Override
|
||||||
public boolean check(View v) {
|
public boolean check(View v) {
|
||||||
|
@ -410,7 +427,6 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
}
|
}
|
||||||
}, 10000));
|
}, 10000));
|
||||||
onView(withId(R.id.vli_volume_text)).check(matches(withText(String.valueOf(volume))));
|
onView(withId(R.id.vli_volume_text)).check(matches(withText(String.valueOf(volume))));
|
||||||
assertTrue(getApplicationHandler().getVolume() == volume);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -434,7 +450,7 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
onView(withId(R.id.mpi_seek_bar)).perform(ViewActions.slideSeekBar(progress));
|
onView(withId(R.id.mpi_seek_bar)).perform(ViewActions.slideSeekBar(progress));
|
||||||
|
|
||||||
onView(withId(R.id.mpi_progress)).check(matches(withText(progressText)));
|
onView(withId(R.id.mpi_progress)).check(matches(withText(progressText)));
|
||||||
assertTrue(getPlayerHandler().getPosition() == progress);
|
assertTrue(getPlayerHandler().getTimeElapsed() == progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -458,7 +474,7 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
onView(withId(R.id.mpi_seek_bar)).perform(ViewActions.slideSeekBar(progress));
|
onView(withId(R.id.mpi_seek_bar)).perform(ViewActions.slideSeekBar(progress));
|
||||||
rotateDevice(getActivity());
|
rotateDevice(getActivity());
|
||||||
|
|
||||||
assertTrue(getPlayerHandler().getPosition() == progress);
|
assertEquals(getPlayerHandler().getTimeElapsed(), progress);
|
||||||
onView(withId(R.id.mpi_progress)).check(matches(withProgress(progressText)));
|
onView(withId(R.id.mpi_progress)).check(matches(withProgress(progressText)));
|
||||||
onView(withId(R.id.mpi_seek_bar)).check(matches(withProgress(progress)));
|
onView(withId(R.id.mpi_seek_bar)).check(matches(withProgress(progress)));
|
||||||
}
|
}
|
||||||
|
@ -560,7 +576,7 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
|
||||||
public void pausePlayback() {
|
public void pausePlayback() {
|
||||||
onView(withId(R.id.npp_play)).perform(click());
|
onView(withId(R.id.npp_play)).perform(click());
|
||||||
|
|
||||||
assertFalse(getPlayerHandler().isPlaying());
|
assertSame(getPlayerHandler().getPlayState(), PlayerHandler.PLAY_STATE.PAUSED);
|
||||||
|
|
||||||
expandPanel();
|
expandPanel();
|
||||||
final int progress = ((SeekBar) getActivity().findViewById(R.id.mpi_seek_bar)).getProgress();
|
final int progress = ((SeekBar) getActivity().findViewById(R.id.mpi_seek_bar)).getProgress();
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.xbmc.kore.R;
|
import org.xbmc.kore.R;
|
||||||
import org.xbmc.kore.host.HostInfo;
|
import org.xbmc.kore.host.HostInfo;
|
||||||
import org.xbmc.kore.host.HostManager;
|
|
||||||
import org.xbmc.kore.jsonrpc.method.Input;
|
import org.xbmc.kore.jsonrpc.method.Input;
|
||||||
import org.xbmc.kore.testhelpers.Utils;
|
import org.xbmc.kore.testhelpers.Utils;
|
||||||
import org.xbmc.kore.tests.ui.AbstractTestClass;
|
import org.xbmc.kore.tests.ui.AbstractTestClass;
|
||||||
|
|
|
@ -57,49 +57,49 @@ public class ButtonTests extends AbstractTestClass<RemoteActivity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void leftControlPadButtonTest() throws InterruptedException {
|
public void leftControlPadButtonTest() {
|
||||||
onView(withId(R.id.left)).perform(click());
|
onView(withId(R.id.left)).perform(click());
|
||||||
|
|
||||||
TestUtils.testHTTPEvent(Input.Left.METHOD_NAME, null);
|
TestUtils.testHTTPEvent(Input.Left.METHOD_NAME, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rightControlPadButtonTest() throws InterruptedException {
|
public void rightControlPadButtonTest() {
|
||||||
onView(withId(R.id.right)).perform(click());
|
onView(withId(R.id.right)).perform(click());
|
||||||
|
|
||||||
TestUtils.testHTTPEvent(Input.Right.METHOD_NAME, null);
|
TestUtils.testHTTPEvent(Input.Right.METHOD_NAME, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void upControlPadButtonTest() throws InterruptedException {
|
public void upControlPadButtonTest() {
|
||||||
onView(withId(R.id.up)).perform(click());
|
onView(withId(R.id.up)).perform(click());
|
||||||
|
|
||||||
TestUtils.testHTTPEvent(Input.Up.METHOD_NAME, null);
|
TestUtils.testHTTPEvent(Input.Up.METHOD_NAME, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void downControlPadButtonTest() throws InterruptedException {
|
public void downControlPadButtonTest() {
|
||||||
onView(withId(R.id.down)).perform(click());
|
onView(withId(R.id.down)).perform(click());
|
||||||
|
|
||||||
TestUtils.testHTTPEvent(Input.Down.METHOD_NAME, null);
|
TestUtils.testHTTPEvent(Input.Down.METHOD_NAME, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void selectPadButtonTest() throws InterruptedException {
|
public void selectPadButtonTest() {
|
||||||
onView(withId(R.id.select)).perform(click());
|
onView(withId(R.id.select)).perform(click());
|
||||||
|
|
||||||
TestUtils.testHTTPEvent(Input.Select.METHOD_NAME, null);
|
TestUtils.testHTTPEvent(Input.Select.METHOD_NAME, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void contextControlPadButtonTest() throws InterruptedException {
|
public void contextControlPadButtonTest() {
|
||||||
onView(withId(R.id.context)).perform(click());
|
onView(withId(R.id.context)).perform(click());
|
||||||
|
|
||||||
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.CONTEXTMENU);
|
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.CONTEXTMENU);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void infoControlPadButtonTest() throws InterruptedException {
|
public void infoControlPadButtonTest() {
|
||||||
HostManager.getInstance(getActivity()).getHostInfo().setKodiVersionMajor(17);
|
HostManager.getInstance(getActivity()).getHostInfo().setKodiVersionMajor(17);
|
||||||
|
|
||||||
onView(withId(R.id.info)).perform(click());
|
onView(withId(R.id.info)).perform(click());
|
||||||
|
@ -108,21 +108,21 @@ public class ButtonTests extends AbstractTestClass<RemoteActivity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void infoControlPadButtonLongClickTest() throws InterruptedException {
|
public void infoControlPadButtonLongClickTest() {
|
||||||
onView(withId(R.id.info)).perform(longClick());
|
onView(withId(R.id.info)).perform(longClick());
|
||||||
|
|
||||||
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.PLAYERPROCESSINFO);
|
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.PLAYERPROCESSINFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void osdControlPadButtonTest() throws InterruptedException {
|
public void osdControlPadButtonTest() {
|
||||||
onView(withId(R.id.osd)).perform(click());
|
onView(withId(R.id.osd)).perform(click());
|
||||||
|
|
||||||
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.OSD);
|
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.OSD);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void backControlPadButtonTest() throws InterruptedException {
|
public void backControlPadButtonTest() {
|
||||||
onView(withId(R.id.back)).perform(click());
|
onView(withId(R.id.back)).perform(click());
|
||||||
|
|
||||||
TestUtils.testHTTPEvent(Input.Back.METHOD_NAME, null);
|
TestUtils.testHTTPEvent(Input.Back.METHOD_NAME, null);
|
||||||
|
|
|
@ -55,7 +55,7 @@ public class KodiPreV17Tests extends AbstractTestClass<RemoteActivity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void infoControlPadButtonLongClickTest() throws InterruptedException {
|
public void infoControlPadButtonLongClickTest() {
|
||||||
onView(withId(R.id.info)).perform(longClick());
|
onView(withId(R.id.info)).perform(longClick());
|
||||||
|
|
||||||
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.CODECINFO);
|
TestUtils.testHTTPEvent(Input.ExecuteAction.METHOD_NAME, Input.ExecuteAction.CODECINFO);
|
||||||
|
|
|
@ -60,8 +60,8 @@ public class Database {
|
||||||
return hostInfo;
|
return hostInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void flush(ContentResolver contentResolver, HostInfo hostInfo) {
|
public static void flush(ContentResolver contentResolver) {
|
||||||
contentResolver.delete(MediaContract.Hosts.buildHostUri(hostInfo.getId()), null, null);
|
contentResolver.delete(MediaContract.Hosts.CONTENT_URI, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HostInfo addHost(Context context) {
|
public static HostInfo addHost(Context context) {
|
||||||
|
|
|
@ -18,6 +18,8 @@ package org.xbmc.kore.testutils;
|
||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Player;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -71,4 +73,72 @@ public class TestUtils {
|
||||||
assertTrue("Id " + key + " not found", entry.getValue());
|
assertTrue("Id " + key + " not found", entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Player.GetItem createMusicItem(int i, int libraryId) {
|
||||||
|
Player.GetItem getItem = new Player.GetItem();
|
||||||
|
getItem.addTrack(i);
|
||||||
|
getItem.addLibraryId(libraryId);
|
||||||
|
getItem.addAlbum("Album 1");
|
||||||
|
getItem.addArtist("Artist 1");
|
||||||
|
getItem.addDisplayartist("Artist 1");
|
||||||
|
getItem.addAlbumArtist("Album Artist 1");
|
||||||
|
getItem.addFanart("image://http%3a%2f%2fmedia.theaudiodb.com%2fimages%2fmedia%2fartist%2ffanart%2fxpptss1381301172.jpg/");
|
||||||
|
getItem.addDuration(240);
|
||||||
|
getItem.addFile("/Users/martijn/Projects/dummymediafiles/media/music/Artist 1/Album 1/Track " + i + ".mp3");
|
||||||
|
getItem.addLabel("Label " + i);
|
||||||
|
getItem.addThumbnail("");
|
||||||
|
getItem.addTitle("Music "+ i);
|
||||||
|
getItem.addType(Player.GetItem.TYPE.song);
|
||||||
|
|
||||||
|
return getItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Player.GetItem createVideoItem(int i, int libraryId) {
|
||||||
|
Player.GetItem getItem = new Player.GetItem(0);
|
||||||
|
getItem.addTrack(i);
|
||||||
|
getItem.addLibraryId(libraryId);
|
||||||
|
getItem.addDirector("Director 1");
|
||||||
|
getItem.addDescription("Description of video " + i);
|
||||||
|
getItem.addGenre("Drama");
|
||||||
|
getItem.addFanart("image://http%3a%2f%2fmedia.theaudiodb.com%2fimages%2fmedia%2fartist%2ffanart%2fxpptss1381301172.jpg/");
|
||||||
|
getItem.addDuration(25);
|
||||||
|
getItem.addFile("/Users/martijn/Projects/dummymediafiles/media/music/Artist 1/Album 1/Track " + i + ".mp3");
|
||||||
|
getItem.addLabel("Label " + i);
|
||||||
|
getItem.addThumbnail("");
|
||||||
|
getItem.addTitle("Video "+ i);
|
||||||
|
getItem.addPlot("Plot " + i);
|
||||||
|
getItem.addYear(2009);
|
||||||
|
getItem.addType(Player.GetItem.TYPE.movie);
|
||||||
|
|
||||||
|
return getItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Player.GetItem createMusicVideoItem(int i, int libraryId) {
|
||||||
|
Player.GetItem getItem = new Player.GetItem(0);
|
||||||
|
getItem.addTrack(i);
|
||||||
|
getItem.addLibraryId(libraryId);
|
||||||
|
getItem.addType(Player.GetItem.TYPE.musicvideo);
|
||||||
|
getItem.addAlbum("...Baby One More Time");
|
||||||
|
getItem.addDirector("Nigel Dick");
|
||||||
|
getItem.addThumbnail("image://http%3a%2f%2fwww.theaudiodb.com%2fimages%2fmedia%2falbum%2fthumb%2fbaby-one-more-time-4dcff7453745a.jpg/");
|
||||||
|
getItem.addYear(1999);
|
||||||
|
getItem.addTitle("(You Drive Me) Crazy");
|
||||||
|
getItem.addLabel("(You Drive Me) Crazy");
|
||||||
|
getItem.addDuration(201);
|
||||||
|
getItem.addGenre("Pop");
|
||||||
|
getItem.addPremiered("1999-01-01");
|
||||||
|
|
||||||
|
return getItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Player.GetItem createPictureItem(int i, int libraryId) {
|
||||||
|
Player.GetItem getItem = new Player.GetItem(0);
|
||||||
|
getItem.addLibraryId(libraryId);
|
||||||
|
getItem.addDescription("Description of picture " + i);
|
||||||
|
getItem.addFile("/Users/martijn/Projects/dummymediafiles/media/music/Artist 1/Album 1/Track " + i + ".mp3");
|
||||||
|
getItem.addYear(2008);
|
||||||
|
getItem.addType(Player.GetItem.TYPE.picture);
|
||||||
|
|
||||||
|
return getItem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,13 @@ import com.squareup.okhttp.internal.Util;
|
||||||
import org.xbmc.kore.utils.LogUtils;
|
import org.xbmc.kore.utils.LogUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -45,7 +45,7 @@ public class MockTcpServer {
|
||||||
|
|
||||||
private ServerSocketFactory serverSocketFactory = ServerSocketFactory.getDefault();
|
private ServerSocketFactory serverSocketFactory = ServerSocketFactory.getDefault();
|
||||||
private ServerSocket serverSocket;
|
private ServerSocket serverSocket;
|
||||||
private boolean started;
|
private boolean running;
|
||||||
private ExecutorService executor;
|
private ExecutorService executor;
|
||||||
private int port = -1;
|
private int port = -1;
|
||||||
private InetSocketAddress inetSocketAddress;
|
private InetSocketAddress inetSocketAddress;
|
||||||
|
@ -62,10 +62,10 @@ public class MockTcpServer {
|
||||||
public interface TcpServerConnectionHandler {
|
public interface TcpServerConnectionHandler {
|
||||||
/**
|
/**
|
||||||
* Processes received input
|
* Processes received input
|
||||||
* @param c character received
|
* @param socket
|
||||||
* @return id of associated response if any, -1 if more input is needed.
|
* @return id of associated response if any, -1 if more input is needed.
|
||||||
*/
|
*/
|
||||||
void processInput(char c);
|
void processInput(Socket socket);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the answer for this handler that should be returned to the server after input has been
|
* Gets the answer for this handler that should be returned to the server after input has been
|
||||||
|
@ -93,8 +93,8 @@ public class MockTcpServer {
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void start(InetSocketAddress inetSocketAddress) throws IOException {
|
public void start(InetSocketAddress inetSocketAddress) throws IOException {
|
||||||
if (started) throw new IllegalStateException("start() already called");
|
if (running) throw new IllegalStateException("start() already called");
|
||||||
started = true;
|
running = true;
|
||||||
this.inetSocketAddress = inetSocketAddress;
|
this.inetSocketAddress = inetSocketAddress;
|
||||||
|
|
||||||
serverSocket = serverSocketFactory.createServerSocket();
|
serverSocket = serverSocketFactory.createServerSocket();
|
||||||
|
@ -106,46 +106,50 @@ public class MockTcpServer {
|
||||||
|
|
||||||
port = serverSocket.getLocalPort();
|
port = serverSocket.getLocalPort();
|
||||||
|
|
||||||
|
LogUtils.LOGD(TAG, "start: server started on " + serverSocket.getInetAddress() + ":" + port);
|
||||||
|
|
||||||
executor.execute(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
while (running) {
|
||||||
acceptConnection();
|
try {
|
||||||
} catch (Throwable e) {
|
Socket socket = acceptConnection();
|
||||||
LogUtils.LOGE(TAG, " failed unexpectedly: " + e);
|
serveConnection(socket);
|
||||||
|
} catch(IOException e){
|
||||||
|
//Socket closed
|
||||||
|
LogUtils.LOGD(TAG, "acceptConnection: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release all sockets and all threads, even if any close fails.
|
|
||||||
Util.closeQuietly(serverSocket);
|
|
||||||
for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext(); ) {
|
|
||||||
Util.closeQuietly(s.next());
|
|
||||||
s.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
executor.shutdown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acceptConnection() throws Exception {
|
private Socket acceptConnection() throws IOException {
|
||||||
while (true) {
|
Socket socket = serverSocket.accept();
|
||||||
Socket socket;
|
|
||||||
try {
|
synchronized (openClientSockets) {
|
||||||
socket = serverSocket.accept();
|
|
||||||
} catch (SocketException e) {
|
|
||||||
//Socket closed
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
openClientSockets.add(socket);
|
openClientSockets.add(socket);
|
||||||
serveConnection(socket);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return socket;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void shutdown() throws IOException {
|
public synchronized void shutdown() throws IOException {
|
||||||
if (!started) return;
|
if (!running) return;
|
||||||
|
|
||||||
if (serverSocket == null) throw new IllegalStateException("shutdown() before start()");
|
if (serverSocket == null) throw new IllegalStateException("shutdown() before start()");
|
||||||
|
|
||||||
serverSocket.close();
|
running = false;
|
||||||
|
|
||||||
|
// Release all sockets and all threads, even if any close fails.
|
||||||
|
for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext(); ) {
|
||||||
|
Socket socket = s.next();
|
||||||
|
Util.closeQuietly(socket);
|
||||||
|
s.remove();
|
||||||
|
}
|
||||||
|
Util.closeQuietly(serverSocket);
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
// Await shutdown.
|
// Await shutdown.
|
||||||
try {
|
try {
|
||||||
|
@ -153,7 +157,7 @@ public class MockTcpServer {
|
||||||
throw new IOException("Gave up waiting for executor to shut down");
|
throw new IOException("Gave up waiting for executor to shut down");
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new AssertionError();
|
LogUtils.LOGD(TAG, "shutdown: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,34 +182,28 @@ public class MockTcpServer {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
handleInput();
|
LogUtils.LOGD(TAG, "serveConnection: handling client " + socket.getInetAddress()
|
||||||
|
+ ":" + socket.getLocalPort());
|
||||||
|
|
||||||
|
connectionHandler.processInput(socket);
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
synchronized (openClientSockets) {
|
||||||
|
openClientSockets.remove(socket);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LogUtils.LOGW(TAG, "processing input from " + socket.getInetAddress() + " failed: " + e);
|
LogUtils.LOGW(TAG, "processing input from " + socket.getInetAddress() + " failed: " + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleInput() throws IOException {
|
|
||||||
InputStreamReader in = new InputStreamReader(socket.getInputStream());
|
|
||||||
|
|
||||||
int i;
|
|
||||||
while ((i = in.read()) != -1) {
|
|
||||||
connectionHandler.processInput((char) i);
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.close();
|
|
||||||
openClientSockets.remove(socket);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
executor.execute(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while ( ! (serverSocket.isClosed() || socket.isClosed()) ) {
|
||||||
sendResponse();
|
sendResponse();
|
||||||
Thread.sleep(100);
|
Thread.sleep(100);
|
||||||
if ( serverSocket.isClosed() )
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LogUtils.LOGW(TAG, " sending response from " + socket.getInetAddress() + " failed: " + e);
|
LogUtils.LOGW(TAG, " sending response from " + socket.getInetAddress() + " failed: " + e);
|
||||||
|
@ -216,9 +214,10 @@ public class MockTcpServer {
|
||||||
|
|
||||||
private void sendResponse() throws IOException {
|
private void sendResponse() throws IOException {
|
||||||
PrintWriter out =
|
PrintWriter out =
|
||||||
new PrintWriter(socket.getOutputStream(), true);
|
new PrintWriter(socket.getOutputStream(), false);
|
||||||
String answer = connectionHandler.getResponse();
|
String answer = connectionHandler.getResponse();
|
||||||
if (answer != null) {
|
if (answer != null) {
|
||||||
|
LogUtils.LOGD(TAG, "serveConnection: sendResponse: " +answer);
|
||||||
out.print(answer);
|
out.print(answer);
|
||||||
out.flush();
|
out.flush();
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,29 +28,20 @@ import java.util.ArrayList;
|
||||||
/**
|
/**
|
||||||
* Simulates Addons JSON-RPC API
|
* Simulates Addons JSON-RPC API
|
||||||
*/
|
*/
|
||||||
public class AddonsHandler implements JSONConnectionHandlerManager.ConnectionHandler {
|
public class AddonsHandler extends ConnectionHandler {
|
||||||
private static final String TAG = LogUtils.makeLogTag(AddonsHandler.class);
|
private static final String TAG = LogUtils.makeLogTag(AddonsHandler.class);
|
||||||
|
|
||||||
private static final String ID_NODE = "id";
|
private static final String ID_NODE = "id";
|
||||||
|
|
||||||
public AddonsHandler() { }
|
public AddonsHandler() { }
|
||||||
|
|
||||||
@Override
|
|
||||||
public ArrayList<JsonResponse> getNotifications() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getType() {
|
public String[] getType() {
|
||||||
return new String[]{Addons.GetAddons.METHOD_NAME};
|
return new String[]{Addons.GetAddons.METHOD_NAME};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<JsonResponse> getResponse(String method, ObjectNode jsonRequest) {
|
public ArrayList<JsonResponse> createResponse(String method, ObjectNode jsonRequest) {
|
||||||
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
||||||
|
|
||||||
int methodId = jsonRequest.get(ID_NODE).asInt(-1);
|
int methodId = jsonRequest.get(ID_NODE).asInt(-1);
|
||||||
|
|
|
@ -31,7 +31,7 @@ import static org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifi
|
||||||
/**
|
/**
|
||||||
* Simulates Application JSON-RPC API
|
* Simulates Application JSON-RPC API
|
||||||
*/
|
*/
|
||||||
public class ApplicationHandler implements JSONConnectionHandlerManager.ConnectionHandler {
|
public class ApplicationHandler extends ConnectionHandler {
|
||||||
private static final String TAG = LogUtils.makeLogTag(ApplicationHandler.class);
|
private static final String TAG = LogUtils.makeLogTag(ApplicationHandler.class);
|
||||||
|
|
||||||
private boolean muted;
|
private boolean muted;
|
||||||
|
@ -40,8 +40,6 @@ public class ApplicationHandler implements JSONConnectionHandlerManager.Connecti
|
||||||
private static final String PARAMS_NODE = "params";
|
private static final String PARAMS_NODE = "params";
|
||||||
private static final String PROPERTIES_NODE = "properties";
|
private static final String PROPERTIES_NODE = "properties";
|
||||||
|
|
||||||
private ArrayList<JsonResponse> jsonNotifications = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the muted state and sends a notification
|
* Sets the muted state and sends a notification
|
||||||
* @param muted
|
* @param muted
|
||||||
|
@ -51,7 +49,7 @@ public class ApplicationHandler implements JSONConnectionHandlerManager.Connecti
|
||||||
this.muted = muted;
|
this.muted = muted;
|
||||||
|
|
||||||
if (notify)
|
if (notify)
|
||||||
jsonNotifications.add(new OnVolumeChanged(muted, volume));
|
addNotification(new OnVolumeChanged(muted, volume));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,22 +61,16 @@ public class ApplicationHandler implements JSONConnectionHandlerManager.Connecti
|
||||||
this.volume = volume;
|
this.volume = volume;
|
||||||
|
|
||||||
if (notify)
|
if (notify)
|
||||||
jsonNotifications.add(new OnVolumeChanged(muted, volume));
|
addNotification(new OnVolumeChanged(muted, volume));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVolume() {
|
public int getVolume() {
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ArrayList<JsonResponse> getNotifications() {
|
|
||||||
ArrayList<JsonResponse> jsonResponses = new ArrayList<>(jsonNotifications);
|
|
||||||
jsonNotifications.clear();
|
|
||||||
return jsonResponses;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
this.volume = 0;
|
this.volume = 0;
|
||||||
this.muted = false;
|
this.muted = false;
|
||||||
}
|
}
|
||||||
|
@ -91,7 +83,7 @@ public class ApplicationHandler implements JSONConnectionHandlerManager.Connecti
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<JsonResponse> getResponse(String method, ObjectNode jsonRequest) {
|
public ArrayList<JsonResponse> createResponse(String method, ObjectNode jsonRequest) {
|
||||||
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
||||||
|
|
||||||
int methodId = jsonRequest.get(ID_NODE).asInt(-1);
|
int methodId = jsonRequest.get(ID_NODE).asInt(-1);
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Martijn Brekhof. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package org.xbmc.kore.testutils.tcpserver.handlers;
|
||||||
|
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
|
||||||
|
import org.xbmc.kore.utils.LogUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
abstract class ConnectionHandler {
|
||||||
|
private static final String TAG = LogUtils.makeLogTag(ConnectionHandler.class);
|
||||||
|
|
||||||
|
private ArrayList<JsonResponse> notifications = new ArrayList<>();
|
||||||
|
private HashSet<String> methodsHandled = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to determine which methods the handler implements
|
||||||
|
* @return list of JSON method names
|
||||||
|
*/
|
||||||
|
abstract String[] getType();
|
||||||
|
|
||||||
|
abstract ArrayList<JsonResponse> createResponse(String method, ObjectNode jsonRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to get any notifications from the handler.
|
||||||
|
* @return {@link JsonResponse} that should be sent to the client or null if there are no notifications
|
||||||
|
*/
|
||||||
|
public ArrayList<JsonResponse> getNotifications() {
|
||||||
|
ArrayList<JsonResponse> list = new ArrayList<>(notifications);
|
||||||
|
notifications.clear();
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the response for the requested method.
|
||||||
|
* @param method requested method
|
||||||
|
* @param jsonRequest json node containing the original request
|
||||||
|
* @return {@link JsonResponse} that should be sent to the client
|
||||||
|
*/
|
||||||
|
public ArrayList<JsonResponse> getResponse(String method, ObjectNode jsonRequest) {
|
||||||
|
ArrayList<JsonResponse> responses = createResponse(method, jsonRequest);
|
||||||
|
methodsHandled.add(method);
|
||||||
|
return responses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state of the handler to its initial state
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
methodsHandled.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for given method to be handled by this handler before returning.
|
||||||
|
* @param method
|
||||||
|
* @param timeOutMillis
|
||||||
|
*/
|
||||||
|
public void waitForMethodHandled(String method, long timeOutMillis) throws TimeoutException {
|
||||||
|
while ((!methodsHandled.contains(method)) && timeOutMillis > 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
timeOutMillis -= 100;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.LOGE(TAG, "Thread.sleep interrupted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timeOutMillis <= 0)
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the list of methods handled by the connection handler.
|
||||||
|
*/
|
||||||
|
public void clearMethodsHandled() {
|
||||||
|
methodsHandled.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNotification(JsonResponse notification) {
|
||||||
|
notifications.add(notification);
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ import java.util.ArrayList;
|
||||||
/**
|
/**
|
||||||
* Simulates Input JSON-RPC API
|
* Simulates Input JSON-RPC API
|
||||||
*/
|
*/
|
||||||
public class InputHandler implements JSONConnectionHandlerManager.ConnectionHandler {
|
public class InputHandler extends ConnectionHandler {
|
||||||
private static final String TAG = LogUtils.makeLogTag(InputHandler.class);
|
private static final String TAG = LogUtils.makeLogTag(InputHandler.class);
|
||||||
|
|
||||||
private static final String ACTION = "action";
|
private static final String ACTION = "action";
|
||||||
|
@ -36,15 +36,6 @@ public class InputHandler implements JSONConnectionHandlerManager.ConnectionHand
|
||||||
private String action;
|
private String action;
|
||||||
private String methodName;
|
private String methodName;
|
||||||
|
|
||||||
@Override
|
|
||||||
public ArrayList<JsonResponse> getNotifications() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getType() {
|
public String[] getType() {
|
||||||
return new String[]{Input.ExecuteAction.METHOD_NAME,
|
return new String[]{Input.ExecuteAction.METHOD_NAME,
|
||||||
|
@ -58,7 +49,7 @@ public class InputHandler implements JSONConnectionHandlerManager.ConnectionHand
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<JsonResponse> getResponse(String method, ObjectNode jsonRequest) {
|
public ArrayList<JsonResponse> createResponse(String method, ObjectNode jsonRequest) {
|
||||||
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
||||||
|
|
||||||
methodName = method;
|
methodName = method;
|
||||||
|
|
|
@ -25,10 +25,13 @@ import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
|
||||||
import org.xbmc.kore.utils.LogUtils;
|
import org.xbmc.kore.utils.LogUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import static org.xbmc.kore.jsonrpc.ApiMethod.ID_NODE;
|
import static org.xbmc.kore.jsonrpc.ApiMethod.ID_NODE;
|
||||||
|
@ -37,143 +40,218 @@ import static org.xbmc.kore.jsonrpc.ApiMethod.METHOD_NODE;
|
||||||
public class JSONConnectionHandlerManager implements MockTcpServer.TcpServerConnectionHandler {
|
public class JSONConnectionHandlerManager implements MockTcpServer.TcpServerConnectionHandler {
|
||||||
public static final String TAG = LogUtils.makeLogTag(JSONConnectionHandlerManager.class);
|
public static final String TAG = LogUtils.makeLogTag(JSONConnectionHandlerManager.class);
|
||||||
|
|
||||||
private HashMap<String, ConnectionHandler> handlersByType = new HashMap<>();
|
private final HashMap<String, ConnectionHandler> handlersByType = new HashMap<>();
|
||||||
private HashSet<ConnectionHandler> handlers = new HashSet<>();
|
|
||||||
private StringBuffer buffer = new StringBuffer();
|
|
||||||
private int amountOfOpenBrackets = 0;
|
private int amountOfOpenBrackets = 0;
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
private int responseCount;
|
//HashMap used to prevent adding duplicate responses for the same methodId when invoking
|
||||||
|
//a handler multiple times.
|
||||||
|
private final HashMap<String, ArrayList<JsonResponse>> clientResponses = new HashMap<>();
|
||||||
|
|
||||||
private HashMap<String, ArrayList<JsonResponse>> clientResponses = new HashMap<>();
|
private final HashMap<String, MethodPendingState> methodIdsHandled = new HashMap<>();
|
||||||
|
private final HashSet<String> notificationsHandled = new HashSet<>();
|
||||||
|
|
||||||
public interface ConnectionHandler {
|
public void addHandler(ConnectionHandler handler) {
|
||||||
/**
|
synchronized (handlersByType) {
|
||||||
* Used to determine which methods the handler implements
|
for (String type : handler.getType()) {
|
||||||
* @return list of JSON method names
|
handlersByType.put(type, handler);
|
||||||
*/
|
}
|
||||||
String[] getType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to get the response from a handler implementing the requested
|
|
||||||
* method.
|
|
||||||
* @param method requested method
|
|
||||||
* @param jsonRequest json node containing the original request
|
|
||||||
* @return {@link JsonResponse} that should be sent to the client
|
|
||||||
*/
|
|
||||||
ArrayList<JsonResponse> getResponse(String method, ObjectNode jsonRequest);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to get any notifications from the handler.
|
|
||||||
* @return {@link JsonResponse} that should be sent to the client or null if there are no notifications
|
|
||||||
*/
|
|
||||||
ArrayList<JsonResponse> getNotifications();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should set the state of the handler to its initial state
|
|
||||||
*/
|
|
||||||
void reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addHandler(ConnectionHandler handler) throws Exception {
|
|
||||||
for(String type : handler.getType()) {
|
|
||||||
handlersByType.put(type, handler);
|
|
||||||
}
|
}
|
||||||
handlers.add(handler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processInput(char c) {
|
public void processInput(Socket socket) {
|
||||||
buffer.append(c);
|
StringBuilder stringBuffer = new StringBuilder();
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStreamReader in = new InputStreamReader(socket.getInputStream());
|
||||||
|
int i;
|
||||||
|
while (!socket.isClosed() && (i = in.read()) != -1) {
|
||||||
|
stringBuffer.append((char) i);
|
||||||
|
if (isEndOfJSONStringReached((char) i)) {
|
||||||
|
processJSONInput(stringBuffer.toString());
|
||||||
|
stringBuffer = new StringBuilder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SocketException e) {
|
||||||
|
// Socket closed
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.LOGD(TAG, "processInput: error reading from socket: " + socket +
|
||||||
|
", buffer holds: " + stringBuffer);
|
||||||
|
LogUtils.LOGE(TAG, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes JSON input on individual characters.
|
||||||
|
* Each iteration should start with an opening accolade { and
|
||||||
|
* end with a closing accolade to indicate a complete JSON string has been
|
||||||
|
* fully processed.
|
||||||
|
* @param c
|
||||||
|
* @return true if a JSON string was fully processed, false otherwise
|
||||||
|
*/
|
||||||
|
private boolean isEndOfJSONStringReached(char c) {
|
||||||
|
//We simply assume well formed JSON input so it should always start with
|
||||||
|
//a {. If we need to filter out other input we need to add an additional check
|
||||||
|
//to detect the first opening accolade.
|
||||||
if ( c == '{' ) {
|
if ( c == '{' ) {
|
||||||
amountOfOpenBrackets++;
|
amountOfOpenBrackets++;
|
||||||
} else if ( c == '}' ) {
|
} else if ( c == '}' ) {
|
||||||
amountOfOpenBrackets--;
|
amountOfOpenBrackets--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( amountOfOpenBrackets == 0 ) {
|
return amountOfOpenBrackets == 0;
|
||||||
String input = buffer.toString();
|
|
||||||
buffer = new StringBuffer();
|
|
||||||
processJSONInput(input);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processJSONInput(String input) {
|
private void processJSONInput(String input) {
|
||||||
try {
|
try {
|
||||||
JsonParser parser = objectMapper.getFactory().createParser(input);
|
synchronized (clientResponses) {
|
||||||
ObjectNode jsonRequest = objectMapper.readTree(parser);
|
LogUtils.LOGD(TAG, "processJSONInput: " + input);
|
||||||
|
JsonParser parser = objectMapper.getFactory().createParser(input);
|
||||||
|
ObjectNode jsonRequest = objectMapper.readTree(parser);
|
||||||
|
|
||||||
int methodId = jsonRequest.get(ID_NODE).asInt();
|
int methodId = jsonRequest.get(ID_NODE).asInt();
|
||||||
String method = jsonRequest.get(METHOD_NODE).asText();
|
String method = jsonRequest.get(METHOD_NODE).asText();
|
||||||
ConnectionHandler connectionHandler = handlersByType.get(method);
|
|
||||||
if ( connectionHandler != null ) {
|
methodIdsHandled.put(String.valueOf(methodId), new MethodPendingState(method));
|
||||||
ArrayList<JsonResponse> responses = connectionHandler.getResponse(method, jsonRequest);
|
|
||||||
if (responses != null) {
|
if (clientResponses.get(String.valueOf(methodId)) != null)
|
||||||
addResponse(methodId, responses);
|
return;
|
||||||
|
|
||||||
|
ConnectionHandler connectionHandler = handlersByType.get(method);
|
||||||
|
if (connectionHandler != null) {
|
||||||
|
ArrayList<JsonResponse> responses = connectionHandler.getResponse(method, jsonRequest);
|
||||||
|
if (responses != null) {
|
||||||
|
clientResponses.put(String.valueOf(methodId), responses);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parser.close();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LogUtils.LOGE(getClass().getSimpleName(), e.getMessage());
|
LogUtils.LOGD(TAG, "processJSONInput: error parsing: " + input);
|
||||||
|
LogUtils.LOGE(TAG, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getResponse() {
|
public String getResponse() {
|
||||||
StringBuffer stringBuffer = new StringBuffer();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
//Handle client responses
|
||||||
synchronized (clientResponses) {
|
synchronized (clientResponses) {
|
||||||
//Handle responses
|
for(Map.Entry<String, ArrayList <JsonResponse>> clientResponse : clientResponses.entrySet()) {
|
||||||
Collection<ArrayList<JsonResponse>> jsonResponses = clientResponses.values();
|
for (JsonResponse jsonResponse : clientResponse.getValue()) {
|
||||||
for (ArrayList<JsonResponse> arrayList : jsonResponses) {
|
LogUtils.LOGD(TAG, "sending response: " + jsonResponse.toJsonString());
|
||||||
for (JsonResponse response : arrayList) {
|
try {
|
||||||
stringBuffer.append(response.toJsonString() + "\n");
|
MethodPendingState methodPending = methodIdsHandled.get(jsonResponse.getId());
|
||||||
|
methodPending.handled = true;
|
||||||
|
stringBuilder.append(jsonResponse.toJsonString()).append("\n");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.LOGD(TAG, "getResponse: Error handling response: " + jsonResponse.toJsonString());
|
||||||
|
LogUtils.LOGW(TAG, "getResponse: " + e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clientResponses.clear();
|
clientResponses.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Handle notifications
|
synchronized (handlersByType) {
|
||||||
for(ConnectionHandler handler : handlers) {
|
//Build a new set to make sure we only handle each handler once, even if it handles
|
||||||
ArrayList<JsonResponse> jsonNotifications = handler.getNotifications();
|
//multiple types.
|
||||||
if (jsonNotifications != null) {
|
HashSet<ConnectionHandler> uniqueHandlers = new HashSet<>(handlersByType.values());
|
||||||
|
|
||||||
|
//Handle notifications
|
||||||
|
for (ConnectionHandler handler : uniqueHandlers) {
|
||||||
|
ArrayList<JsonResponse> jsonNotifications = handler.getNotifications();
|
||||||
for (JsonResponse jsonResponse : jsonNotifications) {
|
for (JsonResponse jsonResponse : jsonNotifications) {
|
||||||
stringBuffer.append(jsonResponse.toJsonString() +"\n");
|
try {
|
||||||
|
notificationsHandled.add(jsonResponse.getMethod());
|
||||||
|
stringBuilder.append(jsonResponse.toJsonString()).append("\n");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.LOGD(TAG, "getResponse: Error handling notification: " + jsonResponse.toJsonString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
responseCount++;
|
if (stringBuilder.length() > 0) {
|
||||||
if (stringBuffer.length() > 0) {
|
return stringBuilder.toString();
|
||||||
return stringBuffer.toString();
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
synchronized (clientResponses) {
|
||||||
|
clearNotificationsHandled();
|
||||||
|
clearMethodsHandled();
|
||||||
|
clientResponses.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearMethodsHandled() {
|
||||||
|
methodIdsHandled.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits until at least one response has been processed before returning
|
* Waits until at least one response has been processed before returning
|
||||||
*/
|
*/
|
||||||
public void waitForNextResponse(long timeOutMillis) throws TimeoutException {
|
public void waitForMethodHandled(String methodName, long timeOutMillis) throws TimeoutException {
|
||||||
responseCount = 0;
|
while (! isMethodHandled(methodName) && (timeOutMillis > 0)) {
|
||||||
while ((responseCount < 2) && (timeOutMillis > 0)) {
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
timeOutMillis -= 500;
|
timeOutMillis -= 500;
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.LOGW(TAG, "waitForNextResponse got interrupted");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (timeOutMillis < 0)
|
if (timeOutMillis <= 0)
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearNotificationsHandled() {
|
||||||
|
notificationsHandled.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits until at least one response has been processed before returning
|
||||||
|
*/
|
||||||
|
public void waitForNotification(String methodName, long timeOutMillis) throws TimeoutException {
|
||||||
|
while (! notificationsHandled.contains(methodName) && (timeOutMillis > 0)) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(500);
|
||||||
|
timeOutMillis -= 500;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.LOGW(TAG, "waitForNextResponse got interrupted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timeOutMillis <= 0)
|
||||||
throw new TimeoutException();
|
throw new TimeoutException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addResponse(int id, ArrayList<JsonResponse> jsonResponses) {
|
private void addResponse(int id, ArrayList<JsonResponse> jsonResponses) {
|
||||||
ArrayList<JsonResponse> responses = clientResponses.get(String.valueOf(id));
|
|
||||||
if (responses == null) {
|
}
|
||||||
responses = new ArrayList<>();
|
|
||||||
synchronized (clientResponses) {
|
private boolean isMethodHandled(String methodName) {
|
||||||
clientResponses.put(String.valueOf(id), responses);
|
for(MethodPendingState methodPending : methodIdsHandled.values()) {
|
||||||
|
if (methodName.contentEquals(methodPending.name)) {
|
||||||
|
return methodPending.handled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
responses.addAll(jsonResponses);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMethodHandled(String methodId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MethodPendingState {
|
||||||
|
boolean handled;
|
||||||
|
String name;
|
||||||
|
|
||||||
|
MethodPendingState(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import java.util.ArrayList;
|
||||||
/**
|
/**
|
||||||
* Simulates JSON RPC JSON-RPC API
|
* Simulates JSON RPC JSON-RPC API
|
||||||
*/
|
*/
|
||||||
public class JSONRPCHandler implements JSONConnectionHandlerManager.ConnectionHandler {
|
public class JSONRPCHandler extends ConnectionHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getType() {
|
public String[] getType() {
|
||||||
|
@ -35,19 +35,9 @@ public class JSONRPCHandler implements JSONConnectionHandlerManager.ConnectionHa
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<JsonResponse> getResponse(String method, ObjectNode jsonRequest) {
|
public ArrayList<JsonResponse> createResponse(String method, ObjectNode jsonRequest) {
|
||||||
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
||||||
jsonResponses.add(new Ping(jsonRequest.get("id").asInt()));
|
jsonResponses.add(new Ping(jsonRequest.get("id").asInt()));
|
||||||
return jsonResponses;
|
return jsonResponses;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ArrayList<JsonResponse> getNotifications() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,185 +16,189 @@
|
||||||
|
|
||||||
package org.xbmc.kore.testutils.tcpserver.handlers;
|
package org.xbmc.kore.testutils.tcpserver.handlers;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
import org.xbmc.kore.jsonrpc.type.GlobalType;
|
import org.xbmc.kore.jsonrpc.type.GlobalType;
|
||||||
import org.xbmc.kore.jsonrpc.type.PlayerType;
|
import org.xbmc.kore.jsonrpc.type.PlayerType;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Player;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Player;
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Playlist;
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnAVStart;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnPause;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnPause;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnPlay;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnPlay;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnPropertyChanged;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnPropertyChanged;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnSeek;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnSeek;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnSpeedChanged;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnSpeedChanged;
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Player.OnStop;
|
||||||
import org.xbmc.kore.utils.LogUtils;
|
import org.xbmc.kore.utils.LogUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler.TYPE.MUSIC;
|
import static org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler.PLAY_STATE.PAUSED;
|
||||||
|
import static org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler.PLAY_STATE.PLAYING;
|
||||||
|
import static org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler.PLAY_STATE.STOPPED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulates Player JSON-RPC API
|
* Simulates Player JSON-RPC API
|
||||||
*/
|
*/
|
||||||
public class PlayerHandler implements JSONConnectionHandlerManager.ConnectionHandler {
|
public class PlayerHandler extends ConnectionHandler {
|
||||||
private static final String TAG = LogUtils.makeLogTag(PlayerHandler.class);
|
private static final String TAG = LogUtils.makeLogTag(PlayerHandler.class);
|
||||||
|
|
||||||
public enum TYPE {
|
|
||||||
MUSIC,
|
|
||||||
MOVIE,
|
|
||||||
EPISODE,
|
|
||||||
MUSICVIDEO,
|
|
||||||
UNKNOWN,
|
|
||||||
PICTURE,
|
|
||||||
CHANNEL
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String[] repeatModes = {
|
public static String[] repeatModes = {
|
||||||
"off",
|
"off",
|
||||||
"one",
|
"one",
|
||||||
"all"
|
"all"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public enum PLAY_STATE {PLAYING, STOPPED, PAUSED}
|
||||||
|
private PLAY_STATE playState = STOPPED;
|
||||||
private int currentRepeatMode;
|
private int currentRepeatMode;
|
||||||
private boolean shuffled;
|
private boolean shuffled;
|
||||||
private boolean playing;
|
private int elapsedTime;
|
||||||
private int position;
|
|
||||||
private long totalTimeSec = 240; // default value
|
|
||||||
|
|
||||||
private TYPE mediaType = MUSIC;
|
private Player.GetItem mediaItem;
|
||||||
|
private List<PlaylistHolder> playlists = new ArrayList<>();
|
||||||
private Player.GetItem mediaItem = createSongItem();
|
private Playlist.playlistID activePlaylistId = Playlist.playlistID.AUDIO;
|
||||||
private String playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
|
private String playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
|
||||||
|
|
||||||
private ArrayList<JsonResponse> notifications = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ArrayList<JsonResponse> getNotifications() {
|
|
||||||
ArrayList<JsonResponse> list = new ArrayList<>(notifications);
|
|
||||||
notifications.clear();
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
this.shuffled = false;
|
this.shuffled = false;
|
||||||
this.currentRepeatMode = 0;
|
this.currentRepeatMode = 0;
|
||||||
this.position = 0;
|
this.elapsedTime = 0;
|
||||||
this.playing = false;
|
this.playState = STOPPED;
|
||||||
setMediaType(MUSIC);
|
playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
|
||||||
|
playlists.clear();
|
||||||
|
setMediaType(Player.GetItem.TYPE.unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getType() {
|
public String[] getType() {
|
||||||
return new String[] {Player.GetActivePlayers.METHOD_NAME,
|
return new String[] {Player.GetActivePlayers.METHOD_NAME,
|
||||||
Player.GetProperties.METHOD_NAME,
|
Player.GetProperties.METHOD_NAME,
|
||||||
Player.GetItem.METHOD_NAME,
|
Player.GetItem.METHOD_NAME,
|
||||||
Player.SetRepeat.METHOD_NAME,
|
Player.SetRepeat.METHOD_NAME,
|
||||||
Player.SetShuffle.METHOD_NAME,
|
Player.SetShuffle.METHOD_NAME,
|
||||||
Player.Seek.METHOD_NAME,
|
Player.Seek.METHOD_NAME,
|
||||||
Player.PlayPause.METHOD_NAME};
|
Player.PlayPause.METHOD_NAME,
|
||||||
|
Player.Stop.METHOD_NAME,
|
||||||
|
Player.Open.METHOD_NAME};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<JsonResponse> getResponse(String method, ObjectNode jsonRequest) {
|
public ArrayList<JsonResponse> createResponse(String method, ObjectNode jsonRequest) {
|
||||||
LogUtils.LOGD(TAG, "getResponse: method="+method);
|
|
||||||
|
|
||||||
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
||||||
JsonNode node = jsonRequest.get("id");
|
|
||||||
JsonResponse response = null;
|
JsonResponse response = null;
|
||||||
int playerId;
|
|
||||||
|
int methodId = jsonRequest.get("id").asInt();
|
||||||
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case Player.GetActivePlayers.METHOD_NAME:
|
case Player.GetActivePlayers.METHOD_NAME:
|
||||||
response = new Player.GetActivePlayers(node.asInt(), 0, playerType);
|
response = handleGetActivePlayers(methodId);
|
||||||
break;
|
break;
|
||||||
case Player.GetProperties.METHOD_NAME:
|
case Player.GetProperties.METHOD_NAME:
|
||||||
response = updatePlayerProperties(createPlayerProperties(node.asInt()));
|
response = updatePlayerProperties(createPlayerProperties(methodId));
|
||||||
break;
|
break;
|
||||||
case Player.GetItem.METHOD_NAME:
|
case Player.GetItem.METHOD_NAME:
|
||||||
mediaItem.setMethodId(node.asInt());
|
response = handleGetItem(methodId);
|
||||||
response = mediaItem;
|
|
||||||
break;
|
break;
|
||||||
case Player.SetRepeat.METHOD_NAME:
|
case Player.SetRepeat.METHOD_NAME:
|
||||||
response = new Player.SetRepeat(node.asInt(), "OK");
|
response = handleSetRepeat(methodId, jsonRequest);
|
||||||
playerId = jsonRequest.get("params").get("playerid").asInt();
|
|
||||||
currentRepeatMode = ++currentRepeatMode % 3;
|
|
||||||
notifications.add(new OnPropertyChanged(repeatModes[currentRepeatMode], null, playerId));
|
|
||||||
break;
|
break;
|
||||||
case Player.SetShuffle.METHOD_NAME:
|
case Player.SetShuffle.METHOD_NAME:
|
||||||
response = new Player.SetShuffle(node.asInt(), "OK");
|
response = handleSetShuffle(methodId, jsonRequest);
|
||||||
playerId = jsonRequest.get("params").get("playerid").asInt();
|
break;
|
||||||
shuffled = !shuffled;
|
case Player.Open.METHOD_NAME:
|
||||||
notifications.add(new OnPropertyChanged(null, shuffled, playerId));
|
response = handleOpen(methodId, jsonRequest);
|
||||||
break;
|
break;
|
||||||
case Player.PlayPause.METHOD_NAME:
|
case Player.PlayPause.METHOD_NAME:
|
||||||
playing = !playing;
|
response = handlePlayPause(methodId, jsonRequest);
|
||||||
int speed = playing ? 1 : 0;
|
|
||||||
response = new Player.PlayPause(node.asInt(), speed);
|
|
||||||
playerId = jsonRequest.get("params").get("playerid").asInt();
|
|
||||||
if (playing)
|
|
||||||
notifications.add(new OnPlay(1580, getMediaItemType(), playerId, speed));
|
|
||||||
else
|
|
||||||
notifications.add(new OnPause(1580, getMediaItemType(), playerId, speed));
|
|
||||||
notifications.add(new OnSpeedChanged(1580, getMediaItemType(), playerId, speed));
|
|
||||||
break;
|
break;
|
||||||
case Player.Seek.METHOD_NAME:
|
case Player.Seek.METHOD_NAME:
|
||||||
position = new GlobalType.Time(jsonRequest.get("params").get("value")).ToSeconds();
|
response = handleSeek(methodId, jsonRequest);
|
||||||
response = new Player.Seek(node.asInt(), (100 * position) / (double) totalTimeSec, position,
|
|
||||||
totalTimeSec);
|
|
||||||
playerId = jsonRequest.get("params").get("playerid").asInt();
|
|
||||||
|
|
||||||
notifications.add(new OnSeek(node.asInt(), getMediaItemType(), playerId,
|
|
||||||
playing ? 1 : 0, 0, position));
|
|
||||||
break;
|
break;
|
||||||
|
case Player.Stop.METHOD_NAME:
|
||||||
|
handleStop();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogUtils.LOGD(TAG, "getResponse: unknown method received: "+method);
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonResponses.add(response);
|
if (response != null)
|
||||||
|
jsonResponses.add(response);
|
||||||
|
|
||||||
return jsonResponses;
|
return jsonResponses;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void setMediaType(Player.GetItem.TYPE mediaType) {
|
||||||
* Sets the returned media type
|
|
||||||
* @param mediaType
|
|
||||||
*/
|
|
||||||
public void setMediaType(TYPE mediaType) {
|
|
||||||
switch (mediaType) {
|
switch (mediaType) {
|
||||||
case MOVIE:
|
case movie:
|
||||||
mediaItem = createMovieItem();
|
|
||||||
playerType = PlayerType.GetActivePlayersReturnType.VIDEO;
|
playerType = PlayerType.GetActivePlayersReturnType.VIDEO;
|
||||||
break;
|
break;
|
||||||
case MUSIC:
|
case song:
|
||||||
mediaItem = createSongItem();
|
|
||||||
playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
|
playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
|
||||||
break;
|
break;
|
||||||
case UNKNOWN:
|
case unknown:
|
||||||
mediaItem = createUnknownItem();
|
|
||||||
playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
|
playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
|
||||||
break;
|
break;
|
||||||
case MUSICVIDEO:
|
case musicvideo:
|
||||||
mediaItem = createMusicVideoItem();
|
|
||||||
playerType = PlayerType.GetActivePlayersReturnType.VIDEO;
|
playerType = PlayerType.GetActivePlayersReturnType.VIDEO;
|
||||||
break;
|
break;
|
||||||
case PICTURE:
|
case picture:
|
||||||
mediaItem = createPictureItem();
|
|
||||||
playerType = PlayerType.GetActivePlayersReturnType.PICTURE;
|
playerType = PlayerType.GetActivePlayersReturnType.PICTURE;
|
||||||
break;
|
break;
|
||||||
case CHANNEL:
|
case channel:
|
||||||
mediaItem = createChannelItem();
|
|
||||||
playerType = PlayerType.GetActivePlayersReturnType.VIDEO;
|
playerType = PlayerType.GetActivePlayersReturnType.VIDEO;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts playing current item in the playlist
|
||||||
|
*/
|
||||||
public void startPlay() {
|
public void startPlay() {
|
||||||
OnPlay onPlay = new OnPlay(1580, getMediaItemType(), 0, 1);
|
if (playlists.size() > 0 && activePlaylistId != null) {
|
||||||
notifications.add(onPlay);
|
mediaItem = playlists.get(activePlaylistId.ordinal()).getCurrentItem();
|
||||||
playing = true;
|
|
||||||
|
if (mediaItem != null) {
|
||||||
|
setMediaType(Player.GetItem.TYPE.valueOf(getMediaItemType()));
|
||||||
|
|
||||||
|
addNotification(new OnPlay(mediaItem.getLibraryId(), getMediaItemType(), getPlayerId(), 1));
|
||||||
|
addNotification(new OnAVStart(mediaItem.getLibraryId(), getMediaItemType(), getPlayerId(), 1));
|
||||||
|
if (playState == PAUSED) {
|
||||||
|
addNotification(new OnSpeedChanged(mediaItem.getLibraryId(), getMediaItemType(), getPlayerId(), 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
playState = PLAYING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startPlay(Playlist.playlistID playlistId, int playlistPosition) {
|
||||||
|
activePlaylistId = playlistId;
|
||||||
|
|
||||||
|
PlaylistHolder playlistHolder = playlists.get(playlistId.ordinal());
|
||||||
|
playlistHolder.setPlaylistIndex(playlistPosition);
|
||||||
|
|
||||||
|
startPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopPlay() {
|
||||||
|
handleStop();
|
||||||
|
addNotification(new OnStop(mediaItem.getLibraryId(), getMediaItemType(), false));
|
||||||
|
this.playState = STOPPED;
|
||||||
|
mediaItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaylists(List<PlaylistHolder> playlists) {
|
||||||
|
this.playlists = playlists;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current media item for the media type set through {@link #setMediaType(TYPE)}
|
* Returns the current media item for the media type set through {@link #setMediaType(Player.GetItem.TYPE)}
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Player.GetItem getMediaItem() {
|
public Player.GetItem getMediaItem() {
|
||||||
|
@ -205,168 +209,151 @@ public class PlayerHandler implements JSONConnectionHandlerManager.ConnectionHan
|
||||||
* Returns the play position of the current media item
|
* Returns the play position of the current media item
|
||||||
* @return the time elapsed in seconds
|
* @return the time elapsed in seconds
|
||||||
*/
|
*/
|
||||||
public long getPosition() {
|
public long getTimeElapsed() {
|
||||||
return position;
|
return elapsedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPlaying() {
|
public PLAY_STATE getPlayState() {
|
||||||
return playing;
|
return playState;
|
||||||
}
|
|
||||||
|
|
||||||
public void setTotalTimeSec(long totalTimeSec) {
|
|
||||||
this.totalTimeSec = totalTimeSec;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMediaItemType() {
|
private String getMediaItemType() {
|
||||||
switch (mediaType) {
|
return mediaItem.getType();
|
||||||
case MOVIE:
|
}
|
||||||
return OnPlay.TYPE_MOVIE;
|
|
||||||
case MUSIC:
|
private int getPlayerId() {
|
||||||
return OnPlay.TYPE_SONG;
|
switch (playerType) {
|
||||||
case UNKNOWN:
|
case PlayerType.GetActivePlayersReturnType.VIDEO:
|
||||||
return OnPlay.TYPE_UNKNOWN;
|
return 0;
|
||||||
case MUSICVIDEO:
|
case PlayerType.GetActivePlayersReturnType.AUDIO:
|
||||||
return OnPlay.TYPE_MUSICVIDEO;
|
return 1;
|
||||||
case PICTURE:
|
case PlayerType.GetActivePlayersReturnType.PICTURE:
|
||||||
return OnPlay.TYPE_PICTURE;
|
return 2;
|
||||||
case CHANNEL:
|
|
||||||
return OnPlay.TYPE_MOVIE;
|
|
||||||
default:
|
default:
|
||||||
return OnPlay.TYPE_SONG;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Player.GetProperties updatePlayerProperties(Player.GetProperties playerProperties) {
|
private Player.GetProperties updatePlayerProperties(Player.GetProperties playerProperties) {
|
||||||
if (playing)
|
if (playState == PLAYING)
|
||||||
position++;
|
elapsedTime++;
|
||||||
|
|
||||||
if ( ( position > totalTimeSec ) && currentRepeatMode != 0 )
|
if ( mediaItem != null ) {
|
||||||
position = 0;
|
if ( elapsedTime > mediaItem.getDuration() && currentRepeatMode != 0 ) {
|
||||||
|
elapsedTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
playerProperties.addPosition(position);
|
playerProperties.addPercentage((elapsedTime * 100 ) / mediaItem.getDuration());
|
||||||
playerProperties.addPercentage((int) ((position * 100 ) / totalTimeSec));
|
}
|
||||||
playerProperties.addTime(0, 0, position, 767);
|
|
||||||
|
playerProperties.addPosition(elapsedTime);
|
||||||
|
playerProperties.addTime(0, 0, elapsedTime, 767);
|
||||||
|
|
||||||
playerProperties.addShuffled(shuffled);
|
playerProperties.addShuffled(shuffled);
|
||||||
playerProperties.addRepeat(repeatModes[currentRepeatMode]);
|
playerProperties.addRepeat(repeatModes[currentRepeatMode]);
|
||||||
|
|
||||||
|
playerProperties.addPlaylistId(activePlaylistId.ordinal());
|
||||||
|
|
||||||
return playerProperties;
|
return playerProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Player.GetProperties createPlayerProperties(int id) {
|
private Player.GetProperties createPlayerProperties(int id) {
|
||||||
Player.GetProperties properties = new Player.GetProperties(id);
|
Player.GetProperties properties = new Player.GetProperties(id);
|
||||||
properties.addPlaylistId(0);
|
properties.addPlaylistId(activePlaylistId.ordinal());
|
||||||
properties.addRepeat(repeatModes[currentRepeatMode]);
|
properties.addRepeat(repeatModes[currentRepeatMode]);
|
||||||
properties.addShuffled(false);
|
properties.addShuffled(false);
|
||||||
properties.addSpeed(playing ? 1 : 0);
|
properties.addSpeed(playState == PLAYING ? 1 : 0);
|
||||||
properties.addTotaltime(0,0,240,41);
|
|
||||||
|
int duration = mediaItem != null ? mediaItem.getDuration() : 0;
|
||||||
|
int hours = duration / 3600;
|
||||||
|
int remainder = (duration - (hours * 3600));
|
||||||
|
int minutes = remainder / 60;
|
||||||
|
int seconds = remainder - (minutes * 60);
|
||||||
|
properties.addTotaltime(hours,minutes, seconds,0);
|
||||||
|
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Player.GetItem createSongItem() {
|
private JsonResponse handleGetItem(int methodId) {
|
||||||
Player.GetItem item = new Player.GetItem();
|
if (playlists.size() > 0) {
|
||||||
item.addAlbum("My Time Is The Right Time");
|
mediaItem = playlists.get(activePlaylistId.ordinal()).getCurrentItem();
|
||||||
item.addAlbumArtist("Alton Ellis");
|
}
|
||||||
item.addArtist("Alton Ellis");
|
|
||||||
item.addDisplayartist("Alton Ellis");
|
|
||||||
item.addDuration(240);
|
|
||||||
item.addFile("/Users/martijn/Projects/dummymediafiles/media/music/Alton Ellis/My Time Is The Right Time/11-I Can't Stand It.mp3");
|
|
||||||
item.addGenre("Reggae");
|
|
||||||
item.addLabel("I Can't Stand It");
|
|
||||||
item.addRating(0);
|
|
||||||
item.addTitle("I Can't Stand It");
|
|
||||||
item.addTrack(11);
|
|
||||||
item.addType(Player.GetItem.TYPE.SONG);
|
|
||||||
item.addYear(2000);
|
|
||||||
|
|
||||||
return item;
|
try {
|
||||||
|
mediaItem = new Player.GetItem(methodId, mediaItem.toJsonString());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogUtils.LOGE(TAG, "handleGetItem: Error creating new Player.GetItem object");
|
||||||
|
}
|
||||||
|
return mediaItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Player.GetItem createMovieItem() {
|
private JsonResponse handleGetActivePlayers(int methodId) {
|
||||||
Player.GetItem item = new Player.GetItem();
|
if (playState == STOPPED) {
|
||||||
item.addTitle("Elephants Dream");
|
return new Player.GetActivePlayers(methodId);
|
||||||
item.addCast("", "Cas Jansen", "Emo");
|
} else {
|
||||||
item.addCast("", "Tygo Gernandt", "Proog");
|
return new Player.GetActivePlayers(methodId, getPlayerId(), playerType);
|
||||||
item.addDuration(660);
|
}
|
||||||
item.addFile("/Users/martijn/Projects/dummymediafiles/media/movies/Elephants Dream (2006).mp4");
|
|
||||||
item.addGenre("Animation");
|
|
||||||
item.addRating(0);
|
|
||||||
item.addType(Player.GetItem.TYPE.MOVIE);
|
|
||||||
item.addYear(2006);
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Player.GetItem createEpisodeItem() {
|
private JsonResponse handleSetRepeat(int methodId, ObjectNode jsonRequest) {
|
||||||
Player.GetItem item = new Player.GetItem();
|
int playerId = getPlayerIdFromJsonRequest(jsonRequest);
|
||||||
item.addShowtitle("According to Jim");
|
currentRepeatMode = ++currentRepeatMode % 3;
|
||||||
item.addCast("image://http%3a%2f%2fthetvdb.com%2fbanners%2factors%2f41995.jpg/", "James Belushi", "Jim");
|
addNotification(new OnPropertyChanged(repeatModes[currentRepeatMode], null, playerId));
|
||||||
item.addCast("image://http%3a%2f%2fthetvdb.com%2fbanners%2factors%2f41994.jpg/", "Courtney Thorne-Smith", "Cheryl");
|
return new Player.SetRepeat(methodId, "OK");
|
||||||
item.addDuration(1800);
|
|
||||||
item.addFile("/Users/martijn/Projects/dummymediafiles/media/movies/Elephants Dream (2006).mp4");
|
|
||||||
item.addGenre("Comedy");
|
|
||||||
item.addRating(7);
|
|
||||||
item.addType(Player.GetItem.TYPE.EPISODE);
|
|
||||||
item.addFirstaired("2001-10-03");
|
|
||||||
item.addEpisode(1);
|
|
||||||
item.addSeason(1);
|
|
||||||
item.addDirector("Andy Cadiff");
|
|
||||||
item.addTitle("Pilot");
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Player.GetItem createMusicVideoItem() {
|
private JsonResponse handleSetShuffle(int methodId, ObjectNode jsonRequest) {
|
||||||
Player.GetItem item = new Player.GetItem();
|
int playerId = getPlayerIdFromJsonRequest(jsonRequest);
|
||||||
item.addType(Player.GetItem.TYPE.MUSICVIDEO);
|
shuffled = !shuffled;
|
||||||
item.addAlbum("...Baby One More Time");
|
addNotification(new OnPropertyChanged(null, shuffled, playerId));
|
||||||
item.addDirector("Nigel Dick");
|
return new Player.SetShuffle(methodId, "OK");
|
||||||
item.addThumbnail("image://http%3a%2f%2fwww.theaudiodb.com%2fimages%2fmedia%2falbum%2fthumb%2fbaby-one-more-time-4dcff7453745a.jpg/");
|
|
||||||
item.addYear(1999);
|
|
||||||
item.addTitle("(You Drive Me) Crazy");
|
|
||||||
item.addLabel("(You Drive Me) Crazy");
|
|
||||||
item.addRuntime(12);
|
|
||||||
item.addGenre("Pop");
|
|
||||||
item.addPremiered("1999-01-01");
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Player.GetItem createChannelItem() {
|
private JsonResponse handleOpen(int methodId, ObjectNode jsonRequest) {
|
||||||
Player.GetItem item = new Player.GetItem();
|
int playlistId = jsonRequest.get("params").get("item").get("playlistid").asInt();
|
||||||
item.addShowtitle("According to Jim");
|
int playlistIndex = jsonRequest.get("params").get("item").get("position").asInt();
|
||||||
item.addCast("image://http%3a%2f%2fthetvdb.com%2fbanners%2factors%2f41995.jpg/", "James Belushi", "Jim");
|
|
||||||
item.addCast("image://http%3a%2f%2fthetvdb.com%2fbanners%2factors%2f41994.jpg/", "Courtney Thorne-Smith", "Cheryl");
|
|
||||||
item.addDuration(1800);
|
|
||||||
item.addFile("/Users/martijn/Projects/dummymediafiles/media/movies/Elephants Dream (2006).mp4");
|
|
||||||
item.addGenre("Comedy");
|
|
||||||
item.addRating(7);
|
|
||||||
item.addType(Player.GetItem.TYPE.EPISODE);
|
|
||||||
item.addFirstaired("2001-10-03");
|
|
||||||
item.addEpisode(1);
|
|
||||||
item.addSeason(1);
|
|
||||||
item.addDirector("Andy Cadiff");
|
|
||||||
item.addTitle("Pilot");
|
|
||||||
item.addType(Player.GetItem.TYPE.CHANNEL);
|
|
||||||
|
|
||||||
return item;
|
startPlay(Playlist.playlistID.values()[playlistId], playlistIndex);
|
||||||
|
|
||||||
|
return new Player.Open(methodId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Player.GetItem createUnknownItem() {
|
private JsonResponse handlePlayPause(int methodId, ObjectNode jsonRequest) {
|
||||||
Player.GetItem item = new Player.GetItem();
|
playState = playState == PLAYING ? PAUSED : PLAYING; //toggle playstate
|
||||||
item.addTitle("Dumpert");
|
|
||||||
item.addCast("", "Martijn Kaiser", "himself");
|
|
||||||
item.addCast("", "", "Skipmode A1");
|
|
||||||
item.addCast("", "", "Sparkline");
|
|
||||||
item.addGenre("Addon");
|
|
||||||
item.addType(Player.GetItem.TYPE.UNKNOWN);
|
|
||||||
|
|
||||||
return item;
|
int speed = playState == PLAYING ? 1 : 0;
|
||||||
|
int itemId = mediaItem.getLibraryId();
|
||||||
|
int playerId = getPlayerIdFromJsonRequest(jsonRequest);
|
||||||
|
|
||||||
|
if (playState == PLAYING)
|
||||||
|
addNotification(new OnPlay(itemId, getMediaItemType(), playerId, speed));
|
||||||
|
else
|
||||||
|
addNotification(new OnPause(itemId, getMediaItemType(), playerId, speed));
|
||||||
|
|
||||||
|
addNotification(new OnSpeedChanged(itemId, getMediaItemType(), playerId, speed));
|
||||||
|
|
||||||
|
return new Player.PlayPause(methodId, speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Player.GetItem createPictureItem() {
|
private JsonResponse handleSeek(int methodId, ObjectNode jsonRequest) {
|
||||||
Player.GetItem item = new Player.GetItem();
|
if (mediaItem == null)
|
||||||
item.addTitle("Kore Artwork");
|
return new Player.Seek(methodId, 0, 0, 0);
|
||||||
item.addFile("/Users/martijn/Projects/Kore/art/screenshots/Kore_Artwork_Concept_2.png");
|
|
||||||
item.addType(Player.GetItem.TYPE.PICTURE);
|
elapsedTime = new GlobalType.Time(jsonRequest.get("params").get("value")).ToSeconds();
|
||||||
return item;
|
int playerId = getPlayerIdFromJsonRequest(jsonRequest);
|
||||||
|
|
||||||
|
addNotification(new OnSeek(methodId, getMediaItemType(), playerId,
|
||||||
|
playState == PLAYING ? 1 : 0, 0, elapsedTime));
|
||||||
|
return new Player.Seek(methodId, (100 * elapsedTime) / (double) mediaItem.getDuration(),
|
||||||
|
elapsedTime, mediaItem.getDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStop() {
|
||||||
|
addNotification(new OnStop(mediaItem.getLibraryId(), getMediaItemType(), false));
|
||||||
|
playState = STOPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPlayerIdFromJsonRequest(ObjectNode jsonRequest) {
|
||||||
|
return jsonRequest.get("params").get("playerid").asInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Martijn Brekhof. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.xbmc.kore.testutils.tcpserver.handlers;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Player;
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Playlist;
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Playlist.OnAdd;
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Playlist.OnClear;
|
||||||
|
import org.xbmc.kore.utils.LogUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates Playlist JSON-RPC API
|
||||||
|
*/
|
||||||
|
public class PlaylistHandler extends ConnectionHandler {
|
||||||
|
private static final String TAG = LogUtils.makeLogTag(PlaylistHandler.class);
|
||||||
|
|
||||||
|
private static final String ID_NODE = "id";
|
||||||
|
private static final String PARAMS_NODE = "params";
|
||||||
|
private static final String PLAYLISTID_NODE = "playlistid";
|
||||||
|
|
||||||
|
private ArrayList<PlaylistHolder> playlists = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
playlists.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getType() {
|
||||||
|
return new String[]{Playlist.GetItems.METHOD_NAME, Playlist.GetPlaylists.METHOD_NAME};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArrayList<JsonResponse> createResponse(String method, ObjectNode jsonRequest) {
|
||||||
|
ArrayList<JsonResponse> jsonResponses = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
int methodId = jsonRequest.get(ID_NODE).asInt(-1);
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case Playlist.GetItems.METHOD_NAME:
|
||||||
|
int playlistId = jsonRequest.get(PARAMS_NODE).get(PLAYLISTID_NODE).asInt(-1);
|
||||||
|
jsonResponses.add(createPlaylist(methodId, playlistId));
|
||||||
|
break;
|
||||||
|
case Playlist.GetPlaylists.METHOD_NAME:
|
||||||
|
jsonResponses.add(new Playlist.GetPlaylists(methodId));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogUtils.LOGD(TAG, "method: " + method + ", not implemented");
|
||||||
|
}
|
||||||
|
return jsonResponses;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Playlist.GetItems createPlaylist(int methodId, int playlistId) {
|
||||||
|
Playlist.GetItems playlistGetItems = new Playlist.GetItems(methodId);
|
||||||
|
|
||||||
|
if (playlists.size() > playlistId) {
|
||||||
|
for (Player.GetItem getItem : playlists.get(playlistId).getItems()) {
|
||||||
|
playlistGetItems.addItem(getItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return playlistGetItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<PlaylistHolder> getPlaylists() {
|
||||||
|
return playlists;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Player.GetItem> getPlaylist(Playlist.playlistID id) {
|
||||||
|
int playlistId = id.ordinal();
|
||||||
|
|
||||||
|
if (playlistId < playlists.size())
|
||||||
|
return playlists.get(playlistId).getItems();
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the playlist and sends the OnClear notification
|
||||||
|
*/
|
||||||
|
public void clearPlaylist(Playlist.playlistID id) {
|
||||||
|
int playlistId = id.ordinal();
|
||||||
|
|
||||||
|
if (playlistId >= playlists.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnClear onClearNotification = new OnClear(playlistId);
|
||||||
|
addNotification(onClearNotification);
|
||||||
|
|
||||||
|
playlists.get(playlistId).clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addItemToPlaylist(Playlist.playlistID id, Player.GetItem item) {
|
||||||
|
int playlistId = id.ordinal();
|
||||||
|
|
||||||
|
while (playlists.size() <= playlistId) {
|
||||||
|
playlists.add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaylistHolder playlist = playlists.get(playlistId);
|
||||||
|
if (playlist == null) {
|
||||||
|
playlist = new PlaylistHolder(playlistId);
|
||||||
|
playlists.set(playlistId, playlist);
|
||||||
|
}
|
||||||
|
playlist.add(item);
|
||||||
|
|
||||||
|
OnAdd onAddNotification = new OnAdd(item.getLibraryId(), item.getType(), playlistId, playlist.getIndexOf(item));
|
||||||
|
addNotification(onAddNotification);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package org.xbmc.kore.testutils.tcpserver.handlers;
|
||||||
|
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PlaylistHolder {
|
||||||
|
private int id;
|
||||||
|
private List<Player.GetItem> items = new ArrayList<>();
|
||||||
|
private int currentIndex;
|
||||||
|
|
||||||
|
PlaylistHolder(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
id = 0;
|
||||||
|
currentIndex = 0;
|
||||||
|
items.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Player.GetItem item) {
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Player.GetItem> getItems() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndexOf(Player.GetItem item) {
|
||||||
|
return items.indexOf(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player.GetItem getCurrentItem() {
|
||||||
|
return items.get(currentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlaylistSize() {
|
||||||
|
return items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaylistIndex(int index) {
|
||||||
|
currentIndex = index;
|
||||||
|
|
||||||
|
if (currentIndex < 0)
|
||||||
|
currentIndex = 0;
|
||||||
|
else if (currentIndex >= items.size())
|
||||||
|
currentIndex = getPlaylistSize() - 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -133,6 +133,10 @@ public abstract class JsonResponse {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setResultToResponse(JsonNode value) {
|
||||||
|
jsonResponse.set(RESULT_NODE, value);
|
||||||
|
}
|
||||||
|
|
||||||
protected void setResultToResponse(boolean value) {
|
protected void setResultToResponse(boolean value) {
|
||||||
jsonResponse.put(RESULT_NODE, value);
|
jsonResponse.put(RESULT_NODE, value);
|
||||||
}
|
}
|
||||||
|
@ -145,6 +149,15 @@ public abstract class JsonResponse {
|
||||||
jsonResponse.put(RESULT_NODE, value);
|
jsonResponse.put(RESULT_NODE, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setLimits(int start, int end, int total) {
|
||||||
|
ObjectNode limits = createObjectNode();
|
||||||
|
limits.put("start", start);
|
||||||
|
limits.put("end", end);
|
||||||
|
limits.put("total", total);
|
||||||
|
|
||||||
|
((ObjectNode) getResultNode(TYPE.OBJECT)).set("limits", limits);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the value to the array in node with the given key.
|
* Adds the value to the array in node with the given key.
|
||||||
* If the array does not exist it will be created
|
* If the array does not exist it will be created
|
||||||
|
@ -189,6 +202,20 @@ public abstract class JsonResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void addToArrayNode(ObjectNode node, String key, JsonNode value) {
|
||||||
|
JsonNode jsonNode = node.get(key);
|
||||||
|
if (jsonNode == null) {
|
||||||
|
jsonNode = objectMapper.createArrayNode();
|
||||||
|
node.set(key, jsonNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonNode.isArray()) {
|
||||||
|
((ArrayNode) jsonNode).add(value);
|
||||||
|
} else {
|
||||||
|
LogUtils.LOGE(TAG, "JsonNode at key: " + key + " not of type ArrayNode." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void addDataToResponse(String parameter, boolean value) {
|
protected void addDataToResponse(String parameter, boolean value) {
|
||||||
getDataNode().put(parameter, value);
|
getDataNode().put(parameter, value);
|
||||||
}
|
}
|
||||||
|
@ -213,6 +240,18 @@ public abstract class JsonResponse {
|
||||||
return jsonResponse;
|
return jsonResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JsonNode getResultNode() {
|
||||||
|
return jsonResponse.get(RESULT_NODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return jsonResponse.get(ID_NODE).asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return jsonResponse.get(METHOD_NODE).asText();
|
||||||
|
}
|
||||||
|
|
||||||
public String toJsonString() {
|
public String toJsonString() {
|
||||||
return jsonResponse.toString();
|
return jsonResponse.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
import org.xbmc.kore.jsonrpc.type.GlobalType;
|
|
||||||
import org.xbmc.kore.jsonrpc.type.PlayerType;
|
import org.xbmc.kore.jsonrpc.type.PlayerType;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonUtils;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonUtils;
|
||||||
|
@ -29,11 +28,31 @@ import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.nodes.SubtitleDetailsN
|
||||||
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.nodes.VideoDetailsNode;
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.nodes.VideoDetailsNode;
|
||||||
import org.xbmc.kore.utils.LogUtils;
|
import org.xbmc.kore.utils.LogUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serverside JSON RPC responses in Methods.Player.*
|
* Serverside JSON RPC responses in Methods.Player.*
|
||||||
*/
|
*/
|
||||||
public class Player {
|
public class Player {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON response for Player.Open request
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* Query: {"jsonrpc":"2.0","method":"Player.Open","id":77,"params":{"item":{"playlistid":0,"position":2}}}
|
||||||
|
* Answer: {"id":77,"jsonrpc":"2.0","result":"OK"}
|
||||||
|
*
|
||||||
|
* @return JSON string
|
||||||
|
*/
|
||||||
|
public static class Open extends JsonResponse {
|
||||||
|
public final static String METHOD_NAME = "Player.Open";
|
||||||
|
|
||||||
|
public Open(int methodId) {
|
||||||
|
super(methodId);
|
||||||
|
setResultToResponse("OK");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON response for Player.Seek request
|
* JSON response for Player.Seek request
|
||||||
*
|
*
|
||||||
|
@ -82,9 +101,18 @@ public class Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Stop extends JsonResponse {
|
||||||
|
public final static String METHOD_NAME = "Player.Stop";
|
||||||
|
}
|
||||||
|
|
||||||
public static class GetActivePlayers extends JsonResponse {
|
public static class GetActivePlayers extends JsonResponse {
|
||||||
public final static String METHOD_NAME = "Player.GetActivePlayers";
|
public final static String METHOD_NAME = "Player.GetActivePlayers";
|
||||||
|
|
||||||
|
public GetActivePlayers(int methodId) {
|
||||||
|
super(methodId);
|
||||||
|
getResultNode(TYPE.ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
public GetActivePlayers(int methodId, int playerId, String type) {
|
public GetActivePlayers(int methodId, int playerId, String type) {
|
||||||
super(methodId);
|
super(methodId);
|
||||||
ObjectNode objectNode = createObjectNode();
|
ObjectNode objectNode = createObjectNode();
|
||||||
|
@ -94,6 +122,7 @@ public class Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class GetProperties extends JsonResponse {
|
public static class GetProperties extends JsonResponse {
|
||||||
public final static String METHOD_NAME = "Player.GetProperties";
|
public final static String METHOD_NAME = "Player.GetProperties";
|
||||||
|
|
||||||
|
@ -199,6 +228,11 @@ public class Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example:
|
||||||
|
* query: {"jsonrpc":"2.0","method":"Player.GetItem","id":4119,"params":{"playerid":0,"properties":["art","artist","albumartist","album","cast","director","displayartist","duration","episode","fanart","file","firstaired","genre","imdbnumber","plot","premiered","rating","resume","runtime","season","showtitle","streamdetails","studio","tagline","thumbnail","title","top250","track","votes","writer","year","description"]}}
|
||||||
|
* answer: {"id":4119,"jsonrpc":"2.0","result":{"item":{"album":"My Time Is the Right Time","albumartist":["Alton Ellis"],"art":{"artist.fanart":"image://http%3a%2f%2fmedia.theaudiodb.com%2fimages%2fmedia%2fartist%2ffanart%2fxpptss1381301172.jpg/"},"artist":["Alton Ellis"],"displayartist":"Alton Ellis","duration":5,"fanart":"image://http%3a%2f%2fmedia.theaudiodb.com%2fimages%2fmedia%2fartist%2ffanart%2fxpptss1381301172.jpg/","file":"/Users/martijn/Projects/dummymediafiles/media/music/Alton Ellis/My Time Is The Right Time/06-Rock Steady.mp3","genre":["Reggae"],"id":14769,"label":"Rock Steady","rating":0,"thumbnail":"","title":"Rock Steady","track":6,"type":"song","votes":0,"year":2000}}}
|
||||||
|
*/
|
||||||
public static class GetItem extends JsonResponse {
|
public static class GetItem extends JsonResponse {
|
||||||
public final static String METHOD_NAME = "Player.GetItem";
|
public final static String METHOD_NAME = "Player.GetItem";
|
||||||
|
|
||||||
|
@ -238,62 +272,64 @@ public class Player {
|
||||||
final static String DESCRIPTION = "description";
|
final static String DESCRIPTION = "description";
|
||||||
final static String LABEL = "label";
|
final static String LABEL = "label";
|
||||||
|
|
||||||
public enum TYPE { UNKNOWN,
|
public enum TYPE { unknown,
|
||||||
MOVIE,
|
movie,
|
||||||
EPISODE,
|
episode,
|
||||||
MUSICVIDEO,
|
musicvideo,
|
||||||
SONG,
|
song,
|
||||||
PICTURE,
|
picture,
|
||||||
CHANNEL
|
channel
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectNode itemNode;
|
private ObjectNode itemNode;
|
||||||
|
|
||||||
public GetItem() {
|
public GetItem() {
|
||||||
super();
|
super();
|
||||||
ObjectNode resultNode = ((ObjectNode) getResultNode(JsonResponse.TYPE.OBJECT));
|
setupItemNode();
|
||||||
itemNode = createObjectNode();
|
|
||||||
resultNode.set(ITEM, itemNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetItem(int methodId) {
|
public GetItem(int methodId) {
|
||||||
super(methodId);
|
super(methodId);
|
||||||
|
setupItemNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetItem(int methodId, String jsonString) throws IOException {
|
||||||
|
super(methodId, jsonString);
|
||||||
|
ObjectNode resultNode = ((ObjectNode) getResultNode(JsonResponse.TYPE.OBJECT));
|
||||||
|
if (resultNode.has(ITEM)) {
|
||||||
|
itemNode = (ObjectNode) resultNode.get(ITEM);
|
||||||
|
} else {
|
||||||
|
setupItemNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupItemNode() {
|
||||||
ObjectNode resultNode = ((ObjectNode) getResultNode(JsonResponse.TYPE.OBJECT));
|
ObjectNode resultNode = ((ObjectNode) getResultNode(JsonResponse.TYPE.OBJECT));
|
||||||
itemNode = createObjectNode();
|
itemNode = createObjectNode();
|
||||||
resultNode.set(ITEM, itemNode);
|
resultNode.set(ITEM, itemNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMethodId(int methodId) {
|
public void addLibraryId(int id) {
|
||||||
getResponseNode().put(ID_NODE, methodId);
|
itemNode.put(ID_NODE, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return library identifier or -1 if not set
|
||||||
|
*/
|
||||||
|
public int getLibraryId() {
|
||||||
|
JsonNode idNode = itemNode.get(ID_NODE);
|
||||||
|
if (idNode != null)
|
||||||
|
return idNode.asInt();
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addType(TYPE type) {
|
public void addType(TYPE type) {
|
||||||
String strType;
|
itemNode.put(TYPE, type.name());
|
||||||
switch (type) {
|
}
|
||||||
case MOVIE:
|
|
||||||
strType = "movie";
|
public String getType() {
|
||||||
break;
|
return itemNode.get(TYPE).textValue();
|
||||||
case EPISODE:
|
|
||||||
strType = "episode";
|
|
||||||
break;
|
|
||||||
case MUSICVIDEO:
|
|
||||||
strType = "musicvideo";
|
|
||||||
break;
|
|
||||||
case SONG:
|
|
||||||
strType = "song";
|
|
||||||
break;
|
|
||||||
case PICTURE:
|
|
||||||
strType = "picture";
|
|
||||||
break;
|
|
||||||
case CHANNEL:
|
|
||||||
strType = "channel";
|
|
||||||
break;
|
|
||||||
case UNKNOWN:
|
|
||||||
default:
|
|
||||||
strType = "unknown";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
itemNode.put(TYPE, strType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addArt(String banner, String poster, String fanart, String thumbnail) {
|
public void addArt(String banner, String poster, String fanart, String thumbnail) {
|
||||||
|
@ -329,6 +365,10 @@ public class Player {
|
||||||
itemNode.put(DURATION, duration);
|
itemNode.put(DURATION, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDuration() {
|
||||||
|
return itemNode.get(DURATION).asInt();
|
||||||
|
}
|
||||||
|
|
||||||
public void addEpisode(int episode) {
|
public void addEpisode(int episode) {
|
||||||
itemNode.put(EPISODE, episode);
|
itemNode.put(EPISODE, episode);
|
||||||
}
|
}
|
||||||
|
@ -369,6 +409,10 @@ public class Player {
|
||||||
itemNode.putObject(RESUME).setAll(createResumeNode(position, total));
|
itemNode.putObject(RESUME).setAll(createResumeNode(position, total));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getRuntime() {
|
||||||
|
return itemNode.get(RUNTIME).asInt();
|
||||||
|
}
|
||||||
|
|
||||||
public void addRuntime(int runtime) {
|
public void addRuntime(int runtime) {
|
||||||
itemNode.put(RUNTIME, runtime);
|
itemNode.put(RUNTIME, runtime);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Martijn Brekhof. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serverside JSON RPC responses in Playlist.*
|
||||||
|
*/
|
||||||
|
public class Playlist {
|
||||||
|
|
||||||
|
public enum playlistID {
|
||||||
|
AUDIO, VIDEO, PICTURE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON response for Playlist.GetItems request
|
||||||
|
*
|
||||||
|
* * Example:
|
||||||
|
* Query: {"jsonrpc":"2.0","method":"Playlist.GetItems","id":48,"params":
|
||||||
|
* {"playlistid":0,"properties":["art","artist","albumartist","album",
|
||||||
|
* "displayartist","episode","fanart","file","season",
|
||||||
|
* "showtitle","studio","tagline","thumbnail","title",
|
||||||
|
* "track","duration","runtime"]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* Answer: {"id":1,"jsonrpc":"2.0","result":{"items":
|
||||||
|
* [
|
||||||
|
* {"album":"My Time Is the Right Time",
|
||||||
|
* "albumartist":[],
|
||||||
|
* "art":{"artist.fanart":"image://http%3a%2f%2fmedia.theaudiodb.com%2fimages%2fmedia%2fartist%2ffanart%2fxpptss1381301172.jpg/"},
|
||||||
|
* "artist":["Alton Ellis"],
|
||||||
|
* "displayartist":"Alton Ellis",
|
||||||
|
* "duration":5,
|
||||||
|
* "fanart":"image://http%3a%2f%2fmedia.theaudiodb.com%2fimages%2fmedia%2fartist%2ffanart%2fxpptss1381301172.jpg/",
|
||||||
|
* "file":"/Users/martijn/Projects/dummymediafiles/media/music/Alton Ellis/My Time Is The Right Time/17-Black Man's Word.mp3",
|
||||||
|
* "id":41,
|
||||||
|
* "label":"Black Man's Word",
|
||||||
|
* "thumbnail":"",
|
||||||
|
* "title":"Black Man's Word",
|
||||||
|
* "track":17,
|
||||||
|
* "type":"song"}
|
||||||
|
* ],
|
||||||
|
* "limits":{"end":1,"start":0,"total":1}}}
|
||||||
|
*
|
||||||
|
* Playlist empty answer : {"id":48,"jsonrpc":"2.0","result":{"limits":{"end":0,"start":0,"total":0}}}
|
||||||
|
*
|
||||||
|
* @return JSON string
|
||||||
|
*/
|
||||||
|
public static class GetItems extends JsonResponse {
|
||||||
|
public final static String METHOD_NAME = "Playlist.GetItems";
|
||||||
|
|
||||||
|
int limitsEnd;
|
||||||
|
|
||||||
|
public GetItems(int id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toJsonString() {
|
||||||
|
setLimits(0, limitsEnd, limitsEnd);
|
||||||
|
return super.toJsonString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addItem(Player.GetItem playerItem) {
|
||||||
|
ObjectNode resultNode = (ObjectNode) getResultNode(TYPE.OBJECT);
|
||||||
|
JsonNode item = playerItem.getResultNode().get(Player.GetItem.ITEM);
|
||||||
|
addToArrayNode(resultNode, "items", item);
|
||||||
|
|
||||||
|
limitsEnd++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON response for Playlist.GetPlaylists response
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* Query: {"jsonrpc":"2.0","method":"Playlist.GetPlaylists","id":31}
|
||||||
|
* Response: {"id":31,"jsonrpc":"2.0","result":[{"playlistid":0,"type":"audio"},{"playlistid":1,"type":"video"},{"playlistid":2,"type":"picture"}]}
|
||||||
|
*/
|
||||||
|
public static class GetPlaylists extends JsonResponse {
|
||||||
|
public final static String METHOD_NAME = "Playlist.GetPlaylists";
|
||||||
|
|
||||||
|
public GetPlaylists(int id) {
|
||||||
|
super(id);
|
||||||
|
|
||||||
|
ArrayNode playlists = createArrayNode();
|
||||||
|
playlists.add(createPlaylistNode(playlistID.AUDIO.ordinal(), "audio"));
|
||||||
|
playlists.add(createPlaylistNode(playlistID.VIDEO.ordinal(), "video"));
|
||||||
|
playlists.add(createPlaylistNode(playlistID.PICTURE.ordinal(), "picture"));
|
||||||
|
|
||||||
|
setResultToResponse(playlists);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectNode createPlaylistNode(int id, String type) {
|
||||||
|
ObjectNode playlistNode = createObjectNode();
|
||||||
|
playlistNode.put("playlistid", id);
|
||||||
|
playlistNode.put("type", type);
|
||||||
|
return playlistNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,15 +24,6 @@ import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonUtils;
|
||||||
public class Player {
|
public class Player {
|
||||||
|
|
||||||
abstract public static class PlayPause extends JsonResponse {
|
abstract public static class PlayPause extends JsonResponse {
|
||||||
public static String TYPE_SONG = "song";
|
|
||||||
public static String TYPE_EPISODE = "episode";
|
|
||||||
public static String TYPE_MOVIE = "movie";
|
|
||||||
public static String TYPE_MUSICVIDEO = "musicvideo";
|
|
||||||
public static String TYPE_VIDEO = "video";
|
|
||||||
public static String TYPE_UNKNOWN = "unknown";
|
|
||||||
public static String TYPE_PICTURE = "picture";
|
|
||||||
public static String TYPE_CHANNEL = "channel";
|
|
||||||
|
|
||||||
private PlayPause(String methodName, int itemId, String itemType, int playerId, int speed) {
|
private PlayPause(String methodName, int itemId, String itemType, int playerId, int speed) {
|
||||||
addMethodToResponse(methodName);
|
addMethodToResponse(methodName);
|
||||||
|
|
||||||
|
@ -93,6 +84,30 @@ public class Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON response for Player.OnStop notification
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* {"jsonrpc":"2.0","method":"Player.OnStop","params":{"data":{"end":false,"item":{"id":14765,"type":"song"}},"sender":"xbmc"}}
|
||||||
|
*/
|
||||||
|
public static class OnStop extends JsonResponse {
|
||||||
|
public final static String METHOD_NAME = "Player.OnStop";
|
||||||
|
|
||||||
|
public OnStop(int itemId, String itemType, boolean ended) {
|
||||||
|
super();
|
||||||
|
addMethodToResponse(METHOD_NAME);
|
||||||
|
|
||||||
|
addDataToResponse("end", false);
|
||||||
|
|
||||||
|
ObjectNode itemNode = createObjectNode();
|
||||||
|
itemNode.put("id", itemId);
|
||||||
|
itemNode.put("type", itemType);
|
||||||
|
addDataToResponse("item", itemNode);
|
||||||
|
|
||||||
|
addParameterToResponse("sender", "xbmc");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON response for Player.OnPropertyChanged notification
|
* JSON response for Player.OnPropertyChanged notification
|
||||||
*
|
*
|
||||||
|
@ -127,7 +142,7 @@ public class Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON response for Player.OnPropertyChanged notification
|
* JSON response for Player.OnSeek notification
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* {"jsonrpc":"2.0","method":"Player.OnSeek", "params":{ "data":{"item":{ "id":127,"type":"episode" },"player":{ "playerid":1,"seekoffset":{ "hours":0,"milliseconds":0, "minutes":0,"seconds":-14 },"speed":0, "time":{"hours":0, "milliseconds":0,"minutes":0, "seconds":2} }},"sender":"xbmc" }}
|
* {"jsonrpc":"2.0","method":"Player.OnSeek", "params":{ "data":{"item":{ "id":127,"type":"episode" },"player":{ "playerid":1,"seekoffset":{ "hours":0,"milliseconds":0, "minutes":0,"seconds":-14 },"speed":0, "time":{"hours":0, "milliseconds":0,"minutes":0, "seconds":2} }},"sender":"xbmc" }}
|
||||||
|
@ -154,4 +169,22 @@ public class Player {
|
||||||
addParameterToResponse("sender", "xbmc");
|
addParameterToResponse("sender", "xbmc");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON response for Player.OnAVStart notification
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* {"jsonrpc":"2.0","method":"Player.OnAVStart",
|
||||||
|
* "params":{"data":{
|
||||||
|
* "item":{"id":1502,"type":"song"},
|
||||||
|
* "player":{"playerid":0,"speed":1}},
|
||||||
|
* "sender":"xbmc"}}
|
||||||
|
*/
|
||||||
|
public static class OnAVStart extends PlayPause {
|
||||||
|
public final static String METHOD_NAME = "Player.OnAVStart";
|
||||||
|
|
||||||
|
public OnAVStart(int itemId, String itemType, int playerId, int speed) {
|
||||||
|
super(METHOD_NAME, itemId, itemType, playerId, speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Martijn Brekhof. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.JsonResponse;
|
||||||
|
|
||||||
|
public class Playlist {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON response for Playlist.OnClear notification
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* {"jsonrpc":"2.0","method":"Playlist.OnClear","params":{"data":{"playlistid":0},"sender":"xbmc"}}
|
||||||
|
*/
|
||||||
|
public static class OnClear extends JsonResponse {
|
||||||
|
public final static String METHOD_NAME = "Playlist.OnClear";
|
||||||
|
|
||||||
|
public OnClear(int playlistId) {
|
||||||
|
super();
|
||||||
|
addMethodToResponse(METHOD_NAME);
|
||||||
|
|
||||||
|
addDataToResponse("playlistid", playlistId);
|
||||||
|
|
||||||
|
addParameterToResponse("sender", "xbmc");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON response for Playlist.OnAdd notification
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* {"jsonrpc":"2.0","method":"Playlist.OnAdd","params":{"data":{"item":{"id":1502,"type":"song"},"playlistid":0,"position":0},"sender":"xbmc"}}
|
||||||
|
*/
|
||||||
|
public static class OnAdd extends JsonResponse {
|
||||||
|
public final static String METHOD_NAME = "Playlist.OnAdd";
|
||||||
|
|
||||||
|
public OnAdd(int itemId, String type, int playlistId, int playlistPosition) {
|
||||||
|
addMethodToResponse(METHOD_NAME);
|
||||||
|
|
||||||
|
ObjectNode item = createObjectNode();
|
||||||
|
item.put("id", itemId);
|
||||||
|
item.put("type", type);
|
||||||
|
addDataToResponse("item", item);
|
||||||
|
|
||||||
|
addDataToResponse("playlistid", playlistId);
|
||||||
|
addDataToResponse("position", playlistPosition);
|
||||||
|
|
||||||
|
addParameterToResponse("sender", "xbmc");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -876,6 +876,7 @@ public class HostConnectionObserver
|
||||||
checkingWhatsPlaying = false;
|
checkingWhatsPlaying = false;
|
||||||
int currentCallResult = (getPropertiesResult.speed == 0) ?
|
int currentCallResult = (getPropertiesResult.speed == 0) ?
|
||||||
PlayerEventsObserver.PLAYER_IS_PAUSED : PlayerEventsObserver.PLAYER_IS_PLAYING;
|
PlayerEventsObserver.PLAYER_IS_PAUSED : PlayerEventsObserver.PLAYER_IS_PLAYING;
|
||||||
|
|
||||||
if (forceReply ||
|
if (forceReply ||
|
||||||
(hostState.lastCallResult != currentCallResult) ||
|
(hostState.lastCallResult != currentCallResult) ||
|
||||||
getPropertiesResultChanged(getPropertiesResult) ||
|
getPropertiesResultChanged(getPropertiesResult) ||
|
||||||
|
@ -887,6 +888,7 @@ public class HostConnectionObserver
|
||||||
forceReply = false;
|
forceReply = false;
|
||||||
// Copy list to prevent ConcurrentModificationExceptions
|
// Copy list to prevent ConcurrentModificationExceptions
|
||||||
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
|
List<PlayerEventsObserver> allObservers = new ArrayList<>(observers);
|
||||||
|
|
||||||
for (final PlayerEventsObserver observer : allObservers) {
|
for (final PlayerEventsObserver observer : allObservers) {
|
||||||
notifySomethingIsPlaying(getActivePlayersResult, getPropertiesResult, getItemResult, observer);
|
notifySomethingIsPlaying(getActivePlayersResult, getPropertiesResult, getItemResult, observer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
android:contentDescription="@string/action_options"/>
|
android:contentDescription="@string/action_options"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/playlist_item_title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_toRightOf="@id/art"
|
android:layout_toRightOf="@id/art"
|
||||||
|
@ -68,9 +68,9 @@
|
||||||
android:id="@+id/details"
|
android:id="@+id/details"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignLeft="@id/title"
|
android:layout_alignLeft="@id/playlist_item_title"
|
||||||
android:layout_alignStart="@id/title"
|
android:layout_alignStart="@id/playlist_item_title"
|
||||||
android:layout_below="@id/title"
|
android:layout_below="@id/playlist_item_title"
|
||||||
android:layout_toRightOf="@id/art"
|
android:layout_toRightOf="@id/art"
|
||||||
android:layout_toEndOf="@id/art"
|
android:layout_toEndOf="@id/art"
|
||||||
android:layout_toLeftOf="@id/list_context_menu"
|
android:layout_toLeftOf="@id/list_context_menu"
|
||||||
|
|
Loading…
Reference in New Issue