SpawnPlayerActivity.java

package com.spawnlabs.endpoint.gamestick.player.ui; 
 
import android.content.Context; 
import android.content.SharedPreferences; 
import android.os.AsyncTask; 
import android.os.Bundle; 
import android.os.Handler; 
import android.util.Log; 
import android.view.InputDevice; 
import android.view.KeyEvent; 
import android.view.LayoutInflater; 
import android.view.MotionEvent; 
import android.view.View; 
import android.widget.FrameLayout; 
import android.widget.RelativeLayout; 
import com.spawnlabs.endpoint.gamestick.player.GameStickApplication; 
import com.spawnlabs.endpoint.gamestick.player.R; 
import com.spawnlabs.endpoint.gamestick.player.event.GameEvent; 
import com.spawnlabs.endpoint.gamestick.player.misc.HealthReportTimer; 
import com.spawnlabs.endpoint.gamestick.player.model.Q3DemoConnectionProperties; 
import com.spawnlabs.endpoint.gamestick.player.model.gameplayer.GameNode; 
import com.spawnlabs.endpoint.gamestick.player.model.gameplayer.GamePlayerAlreadyPlayingException; 
import com.spawnlabs.endpoint.gamestick.player.model.gameplayer.GamePlayerException; 
import com.spawnlabs.endpoint.gamestick.player.model.gameplayer.GamePlayerWarningException; 
import com.spawnlabs.endpoint.gamestick.player.model.play.Session; 
import com.spawnlabs.endpoint.gamestick.player.model.scoring.ScoringResources; 
import com.spawnlabs.endpoint.gamestick.player.service.play.PlayService; 
import com.spawnlabs.endpoint.gamestick.player.service.play.PlaySessionLogService; 
import com.spawnlabs.endpoint.gamestick.player.service.spawnrest.SpawnRestException; 
import com.spawnlabs.endpoint.gamestick.player.service.spawnrest.SpawnRestNotLoggedInException; 
import com.spawnlabs.endpoint.gamestick.player.service.spawnrest.SpawnRestValidationException; 
import com.spawnlabs.endpoint.gamestick.player.ui.adroit.AdroitComponent; 
import com.spawnlabs.endpoint.gamestick.player.ui.adroit.AdroitComponentParameters; 
import com.spawnlabs.endpoint.gamestick.player.ui.adroit.AdroitPlayerException; 
import com.spawnlabs.endpoint.gamestick.player.util.DialogUtil; 
import com.spawnlabs.endpoint.gamestick.player.util.ScoringUtil; 
import com.spawnlabs.endpoint.gamestick.player.util.StringUtil; 
import com.spawnlabs.endpoint.scoring.ScoringClientException; 
 
import java.util.Arrays; 
import java.util.HashSet; 
import java.util.List; 
import java.util.Set; 
 
public class SpawnPlayerActivity 
        extends MenuBaseActivity { 
 
    private static final String TAG = "GS-" + SpawnPlayerActivity.class.getSimpleName(); 
    private static final String CONTROLTAG = TAG + "-CONTROL-"; 
 
    public static final String PLAYED_GAMES = "played_games"; 
 
    protected final Handler handler = new Handler(); 
    protected GameNode gameNode; 
    protected AdroitComponent adroitComponent; 
    private FrameLayout fLayout; 
 
    private PlaySessionData playSessionData = new PlaySessionData();
public SpawnPlayerActivity() { gameNode = new GameNode(); }
private static List<MenuButton> menuButtons = Arrays.asList(new MenuButton[]{ new MenuButton(R.id.navContinueButton, R.id.navContinueButtonShadow, new ContinueAction()), new MenuButton(R.id.navSettingsButton, R.id.navSettingsButtonShadow, SettingsActivity.class), new MenuButton(R.id.navControlsButton, R.id.navControlsButtonShadow, (MenuAction)null), // TODO: Make this do something new MenuButton(R.id.navQuitButton, R.id.navQuitButtonShadow, new QuitAction()), new MenuButton(R.id.navHelpButton, R.id.navHelpButtonShadow, HelpActivity.class) });
@Override
protected List<MenuButton> getMenuButtons() { return menuButtons; } // ======================================================================================================== // Lifecycle methods // ========================================================================================================
@Override
protected void onCreate(Bundle savedInstanceState) { Log.v(TAG, "onCreate()"); getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); fLayout = (FrameLayout) layoutInflater.inflate(R.layout.activity_spawn_player, null); setContentView(fLayout); adroitComponent = new AdroitComponent((RelativeLayout) fLayout.findViewById(R.id.surfaceRelativeLayout), this, new AdroitComponentCallback(), createAdroitComponentParameters(true), createAdroitComponentParameters(false)); super.onCreate(savedInstanceState); }
@Override
protected void onStart() { Log.v(TAG, "onStart()"); super.onStart(); beginPlaySession(); }
@Override
protected void onStop() { Log.v(TAG, "onStop()"); super.onStop(); stopPlaying(); } // ======================================================================================================== // Play session handling // ========================================================================================================
protected void beginPlaySession() { Log.v(TAG, "beginPlaySession()"); GameStickApplication.get().setPlaying(true); playSessionData.clear(); playSessionData.gameId = getIntent().getStringExtra(NavMenuBaseActivity.GAME_ID_EXTRA); playSessionData.userId = getUserId(); addGameToGamesPlayed(); new ScoringAsyncTask(playSessionData.gameId).execute(); }
/** * Called from PlaySessionAsyncTask */ public void startPlaying(Session session) { if (session == null || StringUtil.isEmpty(session.token)) { Log.e(TAG, "startPlaying(" + (session == null ? "session=Null" : session.token) + ")"); showErrorDialogAndClose("Failed to acquire session id from game node: " + (session == null ? "[NULL session]" : session.hdPublicUri)); return; } Log.v(TAG, "startPlaying(" + session.token + "|" + session.hdPublicUri + ")"); getApp().getBus().fire(GameEvent.started()); // Preserve the sessionId playSessionData.sessionId = session.token; // The signals to know if we had a problem with either action below: Exception exception = null; try { String[] split = session.hdPublicUri.split(":");// ie: tcp://10.0.1.152:20010 String host = split[1].substring(2); String port = split[2]; String rtspUrl = startNodePlaySession(session.token, host, Integer.parseInt(port)); Log.i(TAG, "rtspUrl: " + rtspUrl); adroitComponent.startAdroitPlayer(rtspUrl); } catch (GamePlayerAlreadyPlayingException e) { // Don't know why we would start play when it's already going. But for now, consider it a warning and do nothing. Log.w(TAG, "Caught GamePlayerAlreadyPlayingException"); } catch (GamePlayerException e) { exception = e; // Hit the signal } catch (AdroitPlayerException e) { exception = e; // Hit the signal } catch (Exception e) { exception = e; // Hit the signal } // Check the signal from above if (exception != null) { String msg = "Gameplay Initialization Error: "; Log.e(TAG, msg, exception); showErrorDialogAndClose(msg + exception.getMessage()); } else { // Everything went well HealthReportTimer.start(gameNode, adroitComponent); // wait a bit, then check if everything is okay before declaring victory. handler.postDelayed(new CheckClientReceivingPacketsRunnable(session.hdPublicUri), 8000); } }
protected void stopPlaying() { Log.v(TAG, "stopPlaying()"); HealthReportTimer.stop(); try { adroitComponent.stopAdroitPlayer(); } catch (AdroitPlayerException e) { // Just log it and move on, since the docs don't tell us what this means Log.e(TAG, "adroitComponent.stopAdroitPlayer() failed"); } try { gameNode.terminatePlaySession(); } catch (GamePlayerWarningException e) { // Just log it and move on Log.e(TAG, "gameNode.terminatePlaySession() failed"); } try { adroitComponent.destroyPlayer1(); } catch (AdroitPlayerException e) { Log.i(TAG, " destroyPlayer() failed"); } // destroyPlayer2(); GameStickApplication.get().setPlaying(false); PlaySessionLogService.uploadLogs(this, playSessionData.userId, playSessionData.sessionId, playSessionData.gameId, playSessionData.nodeId); // todo nodeId is still null... getApp().getBus().fire(GameEvent.stopped()); } // ======================================================================================================== // Control events (keys, mouse, joystick) // ========================================================================================================
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) { // Log.v(CONTROLTAG + ".onKey", "Down: " + keyCode); // No matter what, START and DELETE bring up the pause dialog if (keyCode == KeyEvent.KEYCODE_BUTTON_START || keyCode == 112/* delete */) { return true; } // Reserve a key to stop playing if (keyCode == KeyEvent.KEYCODE_ESCAPE) { finish(); return true; } if (isNavShowing() && keyCode == KeyEvent.KEYCODE_BUTTON_A) { return true; } else if (isNavShowing()) { return super.onKeyUp(keyCode, event); } else if (gameNode.isPlaying()) { sendKeyInputToApu(keyCode, event, true); } return true; }
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) { // Log.v(CONTROLTAG + ".onKey", "Up: " + keyCode); if (isNavShowing() && keyCode == KeyEvent.KEYCODE_BUTTON_A) { return true; } else if (isNavShowing() || keyCode == KeyEvent.KEYCODE_BUTTON_START ) { return super.onKeyUp(keyCode, event); } else if (gameNode.isPlaying()) { sendKeyInputToApu(keyCode, event, false); } return true; }
void sendKeyInputToApu(int keyCode, KeyEvent event, boolean keyDown) { int keyPosition = keyDown ? 1 : 0; boolean isNotMouse = (event.getDevice().getSources() & InputDevice.SOURCE_MOUSE) == 0; if ((isNotMouse) && ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0)) { // Log.v(CONTROLTAG, "Calling sendKeyBoardInput() on node" + ": " + keyCode + " " + keyPosition); try { gameNode.sendKeyBoardInput(keyCode, keyPosition); } catch (GamePlayerWarningException e) { // Just log it and move on Log.e(TAG, "gameNode.sendKeyBoardInput(keyCode, keyPosition) failed"); } } else { // if ((event.getSource() & InputDevice.SOURCE_GAMEPAD) != 0) { // Log.v(CONTROLTAG, "Calling sendGamePadInput() on node" + ": " + keyCode + " " + keyPosition); try { gameNode.sendGamePadInput(keyCode, keyPosition); } catch (GamePlayerWarningException e) { // Just log it and move on Log.e(TAG, "gameNode.sendGamePadInput(keyCode, keyPosition) failed"); } } }
@Override
public boolean onGenericMotionEvent(MotionEvent event) { // Log.v(CONTROLTAG, "onGenericMotionEvent() " + event.toString() + "Device id: " + event.getDeviceId() + "source: " + event.getSource()); if (isNavShowing()) { return false; } if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { if (gameNode.isPlaying()) { JoystickEvent je = new JoystickEvent(event); try { gameNode.sendMotionInput(je.leftTrigger, je.rightTrigger, je.leftJoystickX, je.leftJoystickY, je.rightJoystickX, je.rightJoystickY); } catch (GamePlayerWarningException e) { // Just log it and move on Log.e(TAG, "gameNode.sendMotionInput() failed"); } } } if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) { if (gameNode.isPlaying()) { MouseEvent me = new MouseEvent(event); try { gameNode.sendMouseInput(me.xAxis, me.yAxis, me.vScroll, me.buttons); } catch (GamePlayerWarningException e) { // Just log it and move on Log.e(TAG, "gameNode.sendMouseInput() failed"); } } } return true; } // ======================================================================================================== // UI methods // ======================================================================================================== // ======================================================================================================== // GameNode control methods // ========================================================================================================
String startNodePlaySession(String sessionId
, String gameNodeIp, int gameNodePort) throws GamePlayerException, GamePlayerAlreadyPlayingException { return gameNode.startPlaySession(gameNodeIp, String.valueOf(gameNodePort), sessionId); } // ======================================================================================================== // Minor methods // ========================================================================================================
private void addGameToGamesPlayed() { SharedPreferences sharedPreferences = getSharedPreferences(PLAYED_GAMES, Context.MODE_PRIVATE); Set<String> playedGames = sharedPreferences.getStringSet(PLAYED_GAMES, new HashSet<String>()); playedGames.add(playSessionData.gameId); sharedPreferences.edit().remove(PLAYED_GAMES).commit(); // Remove first, cuz there's a bug in putStringSet() // that FAILS to commit to persistent storage! sharedPreferences.edit().putStringSet(PLAYED_GAMES, playedGames).commit(); }
void showErrorDialogAndClose(String msg) { if (!isFinishing()) { alert(msg, new ErrorDialogCloseCallback()); } }
/** * Q3DEMO1 MPU: * 66.195.107.45 * 10.0.3.110 * <p/> * Q3DEMO2 MPU: 66.195.107.46 10.0.3.112 */ String getGameNodeIp() { return Q3DemoConnectionProperties.getGameNodeIp(playSessionData.gameId); }
int getGameNodePort() { return Q3DemoConnectionProperties.getGameNodePort(); }
int getGameNodeApiPort() { return Q3DemoConnectionProperties.getGameNodeApiPort(); }
private AdroitComponentParameters createAdroitComponentParameters(boolean playerOne) { if (playerOne) { String inputPlyrString = getResources().getString(R.string.profile1Ip); return new AdroitComponentParameters(inputPlyrString); } else { String inputPlyrString2 = getResources().getString(R.string.profile2Ip); return new AdroitComponentParameters(inputPlyrString2); } }
private String getUserId() { try { return GameStickApplication.get().getUser().getUserId(); } catch (SpawnRestNotLoggedInException e) { // Shouldn't happen. Log.e(TAG, "Somehow, the app allowed game streaming to start without the user being logged in. Program error."); finish();// Really can't happen so don't bother calling showErrorDialogAndClose() return null; } }
// ======================================================================================================== // Inner classes // ======================================================================================================== class CheckClientReceivingPacketsRunnable implements Runnable { private final String TAG = CheckClientReceivingPacketsRunnable.class.getSimpleName(); private String hdPublicUri;
public CheckClientReceivingPacketsRunnable(String hdPublicUri) { this.hdPublicUri = hdPublicUri; }
@Override
public void run() { try { if (!HealthReportTimer.isClientReceivingPackets()) { Log.e(TAG, "Client not receiving packets"); showErrorDialogAndClose("Session created successfully, but client is not receiving game stream packets from " + hdPublicUri); } } catch (HealthReportTimer.ClientNotStartedException e) { Log.w(TAG, "HealthReportTimer queried about packets, but wasn't started yet."); } } }
class JoystickEvent { short leftTrigger; short rightTrigger; short leftJoystickX; short leftJoystickY; short rightJoystickX; short rightJoystickY;
JoystickEvent(MotionEvent event) { leftTrigger = (
short) (event.getAxisValue(MotionEvent.AXIS_BRAKE) * 255); rightTrigger = (short) (event.getAxisValue(MotionEvent.AXIS_GAS) * 255); leftJoystickX = (short) (event.getAxisValue(MotionEvent.AXIS_X) * 32767); leftJoystickY = (short) (event.getAxisValue(MotionEvent.AXIS_Y) * 32767); rightJoystickX = (short) (event.getAxisValue(MotionEvent.AXIS_Z) * 32767); rightJoystickY = (short) (event.getAxisValue(MotionEvent.AXIS_RZ) * 32767); } }
class MouseEvent { int xAxis; int yAxis; short vScroll; short buttons;
MouseEvent(MotionEvent event) { xAxis = (
int) (event.getAxisValue(MotionEvent.AXIS_X) * (65536 / 1280)); yAxis = (int) (event.getAxisValue(MotionEvent.AXIS_Y) * (65536 / 720)); vScroll = (short) (event.getAxisValue(MotionEvent.AXIS_VSCROLL) * (32767 / 720)); buttons = (short) event.getButtonState(); } }
class ErrorDialogCloseCallback implements Runnable { @Override public void run() { finish(); } }
class AdroitComponentCallback implements AdroitComponent.AdroitComponentCallback { @Override public void surfaceDestroyed() { SpawnPlayerActivity.this.finish(); }
@Override
public void surfaceRecreated() { fLayout.bringToFront(); } }
private class PlaySessionData { private String userId; private String sessionId; private String gameId; private String nodeId;
private void clear() { userId = null; sessionId = null; gameId = null; nodeId = null; } }
private static class QuitAction implements MenuAction { @Override public void performAction(MenuBaseActivity activity) { activity.finish(); } }
private static class ContinueAction implements MenuAction { @Override public void performAction(MenuBaseActivity activity) { activity.deactivateNavMenu(); } }
private class ScoringAsyncTask extends AsyncTask<Void, Void, ScoringResources> { private final String TAG = "GS-" + ScoringAsyncTask.class.getSimpleName(); private String errMsg; private String gameId;
public ScoringAsyncTask(String gameId) { this.gameId = gameId; }
@Override
protected ScoringResources doInBackground(Void... params) { Log.v(TAG, "doInBackground()"); try { return ScoringUtil.doScoring(SpawnPlayerActivity.this); } catch (SpawnRestException e) { String msg = "Error retrieving scoring servers: "; Log.e(TAG, msg, e); errMsg = msg + e.getMessage(); return null; } catch (ScoringClientException e) { String msg = "Scoring failed: "; Log.e(TAG, msg, e); errMsg = msg + e.getMessage(); return null; } }
@Override
protected void onPostExecute(ScoringResources scoringResources) { Log.v(TAG, "onPostExecute()"); if (scoringResources == null) { Log.e(TAG, "Scoring failed"); if (errMsg != null) { showErrorDialogAndClose(errMsg); } return; } Log.v(TAG, "completed scoringResources = " + scoringResources); new PlaySessionAsyncTask(gameId).execute(scoringResources); } }
private class PlaySessionAsyncTask extends AsyncTask<ScoringResources, Integer, PlaySessionAsyncTask.PlaySessionResult> { private final String TAG = "GS-" + PlaySessionAsyncTask.class.getSimpleName(); private DialogUtil.DialogHandle dialogHandle; private String gameId;
public PlaySessionAsyncTask(String gameId) { this.gameId = gameId; }
@Override
protected PlaySessionAsyncTask.PlaySessionResult doInBackground(ScoringResources... scoringResources) { dialogHandle = DialogUtil.globalProgress(SpawnPlayerActivity.this, R.string.msg_acquiring_game_session, 0); try { // POST /plays String playSessionId = PlayService.getPlaySessionId(SpawnPlayerActivity.this, scoringResources[0], gameId); Log.v(TAG, "playSessionId = " + playSessionId); // Poll GET /plays/:id long start = System.currentTimeMillis(); Log.i(TAG, "Polling service for ready play session..."); while (!PlayService.isPlaySessionReady(SpawnPlayerActivity.this, playSessionId)) { long now = System.currentTimeMillis(); if ((now - start) >= getResources().getInteger(R.integer.GET_SESSION_ID_TIMEOUT)) { String timeoutMsg = getString(R.string.msg_timeout_waiting_for_playsession); Log.e(TAG, timeoutMsg); return new PlaySessionResult().fail(timeoutMsg); } waitAtick(); } Log.i(TAG, "Play session " + playSessionId + " READY."); // GET /plays/:id/session Session playSession = PlayService.getPlaySession(SpawnPlayerActivity.this, playSessionId); Log.v(TAG, "playSession = " + playSession);// todo -EJT cleanup // ------------------------------------------------------------------------------------ try { for (int i = 0; i < 7; i++) {// todo -EJT cleanup Temporary workaround for slow MPU Thread.sleep(100); dialogHandle.progress(i, 6); } } catch (InterruptedException e) { e.printStackTrace(); } // ------------------------------------------------------------------------------------ return new PlaySessionResult().win(playSession); } catch (SpawnRestValidationException e) { // from getPlaySessionId() String msg = getString(R.string.msg_inadequate_connectivity_for_playsession); Log.e(TAG, msg, e); return new PlaySessionResult().fail(msg + " " + e.getMessage()); } catch (SpawnRestException e) { String msg = getString(R.string.msg_failed_to_get_sessionid_from_playservice); Log.e(TAG, msg, e); return new PlaySessionResult().fail(msg + " " + e.getMessage()); } finally { dialogHandle.dismiss(); } }
@Override
protected void onPostExecute(PlaySessionResult playSessionResult) { if (!playSessionResult.success) { showErrorDialogAndClose(playSessionResult.errMsg); return; } startPlaying(playSessionResult.playSession); }
private void waitAtick() { try { Thread.sleep(200); } catch (InterruptedException ignored) {} }
class PlaySessionResult { Session playSession; boolean success; private String errMsg;
PlaySessionResult win(Session playSession) {
this.playSession = playSession; success = true; return this; }
PlaySessionResult fail(String errMsg) {
this.errMsg = errMsg; success = false; return this; } } } }