Skip to content
This repository has been archived by the owner on Jul 16, 2020. It is now read-only.

Commit

Permalink
feat(broadcasting-state): #83 Add interactive_get_user to get user an…
Browse files Browse the repository at this point in the history
…d channel broadcasting state. (#84)
  • Loading branch information
JoshSnider authored Jun 20, 2018
1 parent bffea83 commit 7837837
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 52 deletions.
8 changes: 4 additions & 4 deletions Tests/MixerTests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>$(SolutionDir)boost;$(SolutionDir)Mixer;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)..\source;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_SCL_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<ExceptionHandling>false</ExceptionHandling>
Expand All @@ -116,7 +116,7 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>$(SolutionDir)boost;$(SolutionDir)Mixer;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)..\source;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;_SCL_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<ExceptionHandling>false</ExceptionHandling>
Expand All @@ -134,7 +134,7 @@
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>$(SolutionDir)boost;$(SolutionDir)Mixer;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)..\source;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_SCL_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<ExceptionHandling>false</ExceptionHandling>
Expand All @@ -154,7 +154,7 @@
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>$(SolutionDir)boost;$(SolutionDir)Mixer;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)..\source;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>NDEBUG;_SCL_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<ExceptionHandling>false</ExceptionHandling>
Expand Down
37 changes: 37 additions & 0 deletions Tests/Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -981,5 +981,42 @@ TEST_CLASS(Tests)

Assert::IsTrue(0 == err);
}

TEST_METHOD(UserDataTest)
{
g_start = std::chrono::high_resolution_clock::now();
interactive_config_debug(interactive_debug_trace, handle_debug_message);

int err = 0;
std::string clientId = CLIENT_ID;
std::string versionId = VERSION_ID;
std::string shareCode = SHARE_CODE;
std::string auth;

ASSERT_NOERR(do_auth(clientId, "", auth));

interactive_session session;
ASSERT_NOERR(interactive_open_session(&session));
ASSERT_NOERR(interactive_set_error_handler(session, handle_error_assert));
ASSERT_NOERR(interactive_connect(session, auth.c_str(), versionId.c_str(), shareCode.c_str(), true));
ASSERT_NOERR(interactive_get_user(session, [](void* context, interactive_session session, const interactive_user* user)
{
Assert::IsTrue(nullptr != user);
std::stringstream logStream;
logStream << "Connecting as: " << user->userName << std::endl;
logStream << "Id: " << user->id << std::endl;
logStream << "Avatar: " << user->avatarUrl << std::endl;
logStream << "Experience: " << user->experience << std::endl;
logStream << "Level: " << user->level << std::endl;
logStream << "Sparks: " << user->sparks << std::endl;
logStream << "Broadcasting: " << (user->isBroadcasting ? "true" : "false") << std::endl;
Logger::WriteMessage(logStream.str().c_str());
}));

Logger::WriteMessage("Disconnecting...");
interactive_close_session(session);

Assert::IsTrue(0 == err);
}
};
}
19 changes: 19 additions & 0 deletions samples/InteractiveSample/InteractiveSample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ std::map<std::string, std::string> controlsByTransaction;
// Display an OAuth consent page to the user.
int authorize(std::string& authorization);

// Handle any errors from the interactive service.
void handle_error(void* context, interactive_session session, int errorCode, const char* errorMessage, size_t errorMessageLength);

// Handle user data.
void handle_user(void* context, interactive_session session, const interactive_user* user);

// Handle completed interactive transactions.
void handle_transaction_complete(void* context, interactive_session session, const char* transactionId, size_t transactionIdLength, unsigned int errorCode, const char* errorMessage, size_t errorMessageLength);

Expand Down Expand Up @@ -64,8 +68,13 @@ int main()
err = interactive_set_transaction_complete_handler(session, handle_transaction_complete);
if (err) return err;

// Connect to the interactive session. Session state is not changed until messages are pumped using interactive_run.
err = interactive_connect(session, authorization.c_str(), INTERACTIVE_ID, SHARE_CODE, false);
if (err) return err;

// Get the connected user's data.
err = interactive_get_user(session, handle_user);
if (err) return err;

// Create a group for participants to view the joystick scene.
err = interactive_create_group(session, "JoystickGroup", "Joystick");
Expand Down Expand Up @@ -165,6 +174,16 @@ void handle_error(void* context, interactive_session session, int errorCode, con
std::cerr << "Unexpected Mixer interactive error: " << errorMessage;
}

void handle_user(void* context, interactive_session session, const interactive_user* user)
{
std::cout << "Connecting as: " << user->userName << std::endl;
std::cout << "Avatar: " << user->avatarUrl << std::endl;
std::cout << "Experience: " << user->experience << std::endl;
std::cout << "Level: " << user->level << std::endl;
std::cout << "Sparks: " << user->sparks << std::endl;
std::cout << "Broadcasting: " << (user->isBroadcasting ? "true" : "false") << std::endl;
}

void handle_interactive_input(void* context, interactive_session session, const interactive_input* input)
{
// Get the participant's Mixer name to give them attribution.
Expand Down
99 changes: 61 additions & 38 deletions samples/InteractiveXboxSample/Game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,54 @@ void handle_error(void* context, interactive_session session, int errorCode, con
OutputDebugStringA(debugLine.c_str());
}

// Handle user data.
void handle_user(void* context, interactive_session session, const interactive_user* user)
{
std::cout << "Connecting as: " << user->userName << std::endl;
std::cout << "Avatar: " << user->avatarUrl << std::endl;
std::cout << "Experience: " << user->experience << std::endl;
std::cout << "Level: " << user->level << std::endl;
std::cout << "Sparks: " << user->sparks << std::endl;
std::cout << "Broadcasting: " << (user->isBroadcasting ? "true" : "false") << std::endl;
}

void handle_input(void* context, interactive_session session, const interactive_input* input)
{
Game* game = (Game*)context;
if ((input_type_key == input->type || input_type_click == input->type) && interactive_button_action_down == input->buttonData.action)
{
// Capture the transaction on button down to deduct sparks
int err = interactive_capture_transaction(session, input->transactionId);
if (!err)
{
game->m_controlsById[input->transactionId] = input->control.id;
}
}
}

void handle_transaction(void* context, interactive_session session, const char* transactionId, size_t transactionIdLength, unsigned int errorCode, const char* errorMessage, size_t errorMessageLength)
{
UNREFERENCED_PARAMETER(session);
UNREFERENCED_PARAMETER(transactionIdLength);
UNREFERENCED_PARAMETER(errorMessageLength);
Game* game = (Game*)context;
if (errorCode)
{
OutputDebugStringA((std::string("ERROR: ") + errorMessage + "(" + std::to_string(errorCode) + ")").c_str());
}
else
{
// Transaction was captured, now execute the most super awesome interactive functionality!
std::string controlId = game->m_controlsById[transactionId];
if (0 == strcmp("GiveHealth", controlId.c_str()))
{
OutputDebugStringA("Giving health to the player!\n");
}
}

game->m_controlsById.erase(transactionId);
}

// Initialize the Direct3D resources required to run.
void Game::Initialize(IUnknown* window)
{
Expand Down Expand Up @@ -149,59 +197,34 @@ void Game::Initialize(IUnknown* window)
return;
}

// Connect to the user's interactive channel, using the interactive project specified by the version ID.
// Open an interactive session. This session will remain open for the duration of this sample but it should be
// closed using interactive_close_session() to avoid memory leaks.
err = interactive_open_session(&m_interactiveSession);
if (err) throw err;

// Register an error handler for any errors from the interactive service.
err = interactive_set_error_handler(m_interactiveSession, handle_error);
if (err) throw err;

// Set the session context to this object so it can be referenced in callbacks.
err = interactive_set_session_context(m_interactiveSession, this);
if (err) throw err;

// Register a callback for button presses.
err = interactive_set_input_handler(m_interactiveSession, [](void* context, interactive_session session, const interactive_input* input)
{
Game* game = (Game*)context;
if ((input_type_key == input->type || input_type_click == input->type) && interactive_button_action_down == input->buttonData.action)
{
// Capture the transaction on button down to deduct sparks
int err = interactive_capture_transaction(session, input->transactionId);
if (!err)
{
game->m_controlsById[input->transactionId] = input->control.id;
}


}
});
err = interactive_set_input_handler(m_interactiveSession, handle_input);
if (err) throw err;

err = interactive_set_transaction_complete_handler(m_interactiveSession, [](void* context, interactive_session session, const char* transactionId, size_t transactionIdLength, unsigned int errorCode, const char* errorMessage, size_t errorMessageLength)
{
UNREFERENCED_PARAMETER(session);
UNREFERENCED_PARAMETER(transactionIdLength);
UNREFERENCED_PARAMETER(errorMessageLength);
Game* game = (Game*)context;
if (errorCode)
{
OutputDebugStringA((std::string("ERROR: ") + errorMessage + "(" + std::to_string(errorCode) + ")").c_str());
}
else
{
// Transaction was captured, now execute the most super awesome interactive functionality!
std::string controlId = game->m_controlsById[transactionId];
if (0 == strcmp("GiveHealth", controlId.c_str()))
{
OutputDebugStringA("Giving health to the player!\n");
}
}

game->m_controlsById.erase(transactionId);
});
// Register a callback for transactions completing.
err = interactive_set_transaction_complete_handler(m_interactiveSession, handle_transaction);
if (err) throw err;

// Connect to the user's interactive channel, using the interactive project specified by the version ID.
err = interactive_connect(m_interactiveSession, authorization.c_str(), INTERACTIVE_ID, SHARE_CODE, true);
if (err) throw err;

// Get the connected user's data.
err = interactive_get_user(m_interactiveSession, handle_user);
if (err) throw err;
}

#pragma region Frame Update
Expand Down
1 change: 1 addition & 0 deletions samples/InteractiveXboxSample/Game.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Game
DX::StepTimer m_timer;

// Mixer interactive session
public:
interactive_session m_interactiveSession;
std::map<std::string, std::string> m_controlsById;
};
Expand Down
31 changes: 31 additions & 0 deletions source/interactivity.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,37 @@ extern "C" {
void interactive_close_session(interactive_session session);
/** @} */

/** @name User
* @{
*/

/// <summary>
/// User data for the connected user. Call <c>interactive_get_user</c> after connecting.
/// </summary>
struct interactive_user
{
unsigned int id;
const char* userName;
bool isBroadcasting;
unsigned int level;
unsigned int experience;
unsigned int sparks;
const char* avatarUrl;
};

/// <summary>
/// Callback for <c>interactive_get_user</c> when the user's data is ready.
/// </summary>
typedef void(*on_interactive_user)(void* context, interactive_session session, const interactive_user* user);

/// <summary>
/// Get the current authenticated user's data. <c>onUser</c> will be called when the request completes.
/// </summary>
/// <remarks>
/// This is a blocking function that waits on network IO. Do not call this from the UI thread.
/// </remarks>
int interactive_get_user(interactive_session session, on_interactive_user onUser);

/** @name Controls
* @{
*/
Expand Down
5 changes: 4 additions & 1 deletion source/internal/http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
namespace mixer_internal
{

typedef std::map<std::string, std::string> http_headers;

struct http_response
{
unsigned int statusCode;
Expand All @@ -18,7 +20,8 @@ class http_client
public:
virtual ~http_client() = 0 {};

virtual int make_request(const std::string& uri, const std::string& requestType, const std::map<std::string, std::string>* headers, const std::string& body, _Out_ http_response& response, unsigned long timeoutMs = 5000) const = 0;
// Make an http request with optional headers
virtual int make_request(const std::string& uri, const std::string& requestType, const http_headers* headers, const std::string& body, _Out_ http_response& response, unsigned long timeoutMs = 5000) const = 0;
};

class http_factory
Expand Down
58 changes: 58 additions & 0 deletions source/internal/interactive_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,64 @@ int interactive_get_state(interactive_session session, interactive_state* state)
return MIXER_OK;
}

int interactive_get_user(interactive_session session, on_interactive_user onUser)
{
if (nullptr == session || nullptr == onUser)
{
return MIXER_ERROR_INVALID_POINTER;
}

interactive_session_internal* sessionInternal = reinterpret_cast<interactive_session_internal*>(session);
if (sessionInternal->authorization.empty())
{
DEBUG_ERROR("Broadcasting check attempted without an authorization string set.");
return MIXER_ERROR_AUTH;
}

std::string currentUserUrl = "https://mixer.com/api/v1/users/current";

http_headers headers;
headers["Authorization"] = sessionInternal->authorization;
http_response response;
memset(&response, 0, sizeof(http_response));
int httpErr = sessionInternal->http->make_request(currentUserUrl, "GET", &headers, "", response);
if (0 != httpErr)
{
DEBUG_ERROR(std::to_string(httpErr) + " Failed to GET /api/v1/users/current");
return MIXER_ERROR_HTTP;
}

rapidjson::Document responseDoc;
responseDoc.Parse(response.body);
if (responseDoc.HasParseError())
{
return MIXER_ERROR_JSON_PARSE;
}

// Validate the response.
if (!responseDoc.HasMember("id") || !responseDoc.HasMember("username") ||
!responseDoc.HasMember("level") || !responseDoc.HasMember("experience") ||
!responseDoc.HasMember("sparks") || !responseDoc.HasMember("avatarUrl") ||
!responseDoc.HasMember("channel") || !responseDoc["channel"].IsObject() ||
!responseDoc["channel"].HasMember("online"))
{
return MIXER_ERROR_UNRECOGNIZED_DATA_FORMAT;
}

interactive_user user;
memset(&user, 0, sizeof(interactive_user));
user.avatarUrl = responseDoc["avatarUrl"].GetString();
user.experience = responseDoc["experience"].GetUint();
user.id = responseDoc["id"].GetUint();
user.isBroadcasting = responseDoc["channel"]["online"].GetBool();
user.level = responseDoc["level"].GetUint();
user.sparks = responseDoc["sparks"].GetUint();
user.userName = responseDoc["username"].GetString();

onUser(sessionInternal->callerContext, session, &user);
return MIXER_OK;
}

int interactive_set_ready(interactive_session session, bool isReady)
{
if (nullptr == session)
Expand Down
2 changes: 1 addition & 1 deletion source/internal/win_http_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ win_http_client::~win_http_client()
{
}

int win_http_client::make_request(const std::string& uri, const std::string& verb, const std::map<std::string, std::string>* headers, const std::string& body, _Out_ http_response& response, unsigned long timeoutMs) const
int win_http_client::make_request(const std::string& uri, const std::string& verb, const http_headers* headers, const std::string& body, _Out_ http_response& response, unsigned long timeoutMs) const
{
// Crack the URI.
// Parse the url with regex in accordance with RFC 3986.
Expand Down
2 changes: 1 addition & 1 deletion source/internal/win_http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class win_http_client : public http_client
win_http_client();
~win_http_client();

int make_request(const std::string& uri, const std::string& requestType, const std::map<std::string, std::string>* headers, const std::string& body, _Out_ http_response& response, unsigned long timeoutMs = 5000) const;
int make_request(const std::string& uri, const std::string& requestType, const http_headers* headers, const std::string& body, _Out_ http_response& response, unsigned long timeoutMs = 5000) const;

private:
hinternet_ptr m_internet;
Expand Down
Loading

0 comments on commit 7837837

Please sign in to comment.