Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cherry pick PR #3277: pre-app recommendation cobalt platform service reference code #3331

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<!--
This is a light weighted demo page used to Pre-app Recommendation Service.
Start a http server by running this python3 command:
python3 -m http.server 8000
Then navigate to Cobalt binary directory, run in Cobalt using this command:
./cobalt --url=http://localhost:8000/cobalt/demos/content/pre-app-recommendation-demo/pre-app-recommendation-demo.html
-->
<!DOCTYPE html>
<meta charset="utf-8">

<body>
<script>
'use strict';
/**
* @param {ArrayBuffer} data to be converted to a String.
*/
function ab2str(data) {
try {
return String.fromCharCode.apply(null, new Uint8Array(data));
} catch (error) {
console.error(`ab2str() error: ${error}, decoding data: ${data}`);
}
}

/**
* @param {String} data to be converted to an ArrayBuffer.
*/
function str2ab(data) {
try {
return Uint8Array.from(data.split(''), (s) => { return s.charCodeAt(0) }).buffer;
} catch (error) {
console.error(`str2ab() error: ${error}, decoding data: ${data}`);
}
}

async function testPreappRecommendationService() {

if (!H5vccPlatformService) {
// H5vccPlatformService is not implemented.
console.error("H5vccPlatformService is not implemented");
return;
}

var RECS_SERVICE_NAME = "com.google.youtube.tv.Recommendations";

if (!H5vccPlatformService.has(RECS_SERVICE_NAME)) {
// RECS_SERVICE_NAME is not implemented.
console.error(`H5vccPlatformService.Has(${RECS_SERVICE_NAME}) returned false.`);
return;
}

/**
* @param {ArrayBuffer} data
*/
function receiveCallback(service, data) {

var str_response = ab2str(data);
console.error(`receiveCallback() receives str_response: ${str_response} but receiveCallback() isn't supported today`);

}

// Open the pre app recommendation service.
var pre_app_recommendation_service = H5vccPlatformService.open(RECS_SERVICE_NAME,
receiveCallback);

// Send method getPartnerId and receive platform response.
var get_partner_id_response = pre_app_recommendation_service.send(str2ab(JSON.stringify({ 'method': 'getPartnerId' })));

try {
var partner_id = ab2str(get_partner_id_response);
console.log(`send() method getPartnerId platform response : ${partner_id}`);
} catch (error) {
console.error(`Error in parsing response from platform for method getPartnerId: ${error}`);
return;
}

// Send method recommend, upsert operation and recs_response.
// recs_response will follow the response format in https://developers.google.com/youtube/recommendations/reference/rest/v1/ListRecommendationsResponse
var upsert_operation = str2ab(JSON.stringify({
'method': 'recommend',
'operation': 'upsert',
'recs_response': {
'shelves': [{
'title': 'top picks for you',
'shelf_items': [{
'title': '25 Best Countries to visit',
'url': 'https://www.youtube.com/tv?v=HcdohhNq8Iw&&launch=launcher',
'images': [{
'uri': 'https://i.ytimg.com/vi/HcdohhNq8Iw/default.jpg',
'width': '120',
'height': '90',
'resolution': 'DEFAULT_RESOLUTION'
}]
}]
}
]
}
}));

var delete_operation = str2ab(JSON.stringify({
'method': 'recommend', 'operation': 'delete'
}));

for (let data of [upsert_operation, delete_operation]) {
var recommend_response = ab2str(pre_app_recommendation_service.send(data));
try {
console.log(`send() method recommend platform response: ${recommend_response}`);
} catch (error) {
console.error(`Error in parsing response from platform for method recommend: ${error}`);
}
}

// Close the service after a timeout. This is in case there is an error on
// the platform and a response is not received in the receiveCallback().
var TIME_BEFORE_CLOSE = 10000;
await new Promise(r => setTimeout(r, TIME_BEFORE_CLOSE));
pre_app_recommendation_service.close();
}

window.onload = () => {
testPreappRecommendationService();
}

</script>
</body>
2 changes: 2 additions & 0 deletions starboard/linux/shared/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ static_library("starboard_platform_sources") {
"//starboard/linux/shared/platform_service.cc",
"//starboard/linux/shared/platform_service.h",
"//starboard/linux/shared/player_components_factory.cc",
"//starboard/linux/shared/pre_app_recommendation_service.cc",
"//starboard/linux/shared/pre_app_recommendation_service.h",
"//starboard/linux/shared/routes.cc",
"//starboard/linux/shared/routes.h",
"//starboard/linux/shared/soft_mic_platform_service.cc",
Expand Down
4 changes: 3 additions & 1 deletion starboard/linux/shared/platform_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "starboard/common/log.h"
#include "starboard/common/string.h"
#include "starboard/configuration.h"
#include "starboard/linux/shared/pre_app_recommendation_service.h"
#include "starboard/linux/shared/soft_mic_platform_service.h"
#include "starboard/shared/starboard/application.h"
#if SB_IS(EVERGREEN_COMPATIBLE)
Expand All @@ -40,7 +41,8 @@ struct cmp_str {
};

std::map<const char*, const void* (*)(), cmp_str> platform_service_registry = {
{kSoftMicPlatformServiceName, GetSoftMicPlatformServiceApi}};
{kSoftMicPlatformServiceName, GetSoftMicPlatformServiceApi},
{kPreappRecommendationServiceName, GetPreappRecommendationServiceApi}};

bool Has(const char* name) {
// Checks whether Cobalt platform service registry has service name
Expand Down
193 changes: 193 additions & 0 deletions starboard/linux/shared/pre_app_recommendation_service.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Copyright 2024 The Cobalt Authors. 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.

#include "starboard/linux/shared/pre_app_recommendation_service.h"

#include <memory>
#include <string>

#include "starboard/common/log.h"
#include "starboard/common/string.h"
#include "starboard/configuration.h"
#include "starboard/linux/shared/platform_service.h"
#include "starboard/shared/starboard/application.h"

// Omit namespace 'linux' due to symbol name conflict with macro 'linux'
namespace starboard {
namespace shared {
namespace {
typedef struct PreAppRecommendationsPlatformServiceImpl
: public PlatformServiceImpl {
// Define additional data field.
// variable_1, variable_2,...
PreAppRecommendationsPlatformServiceImpl(
void* context,
ReceiveMessageCallback receive_callback)
: PlatformServiceImpl(context, receive_callback) {}

// Default constructor.
PreAppRecommendationsPlatformServiceImpl() = default;

} PreAppRecommendationsPlatformServiceImpl;

// Use HTTP status code in response to YouTube application's method recommend
// call.
const char kSuccess[] = "200";
const char kBadRequest[] = "400";
// Methods supported
const char kGetPartnerIdMethod[] = "getPartnerId";
const char kRecommendMethod[] = "recommend";
// Operations supported
const char kUpsertOp[] = "upsert";
const char kDeleteOp[] = "delete";
// Configure partner Id
const char kPartnerId[] = "dummy_partner_id";

bool Has(const char* name) {
// Check if platform has service name.
return strcmp(name, kPreappRecommendationServiceName) == 0;
}

PlatformServiceImpl* Open(void* context,
ReceiveMessageCallback receive_callback) {
SB_DCHECK(context);
SB_LOG(INFO) << "Open() service created: "
<< kPreappRecommendationServiceName;

return new PreAppRecommendationsPlatformServiceImpl(context,
receive_callback);
}

void Close(PlatformServiceImpl* service) {
// Function Close shouldn't manually delete PlatformServiceImpl pointer,
// because it is managed by unique_ptr in Platform Service.
SB_LOG(INFO)
<< kPreappRecommendationServiceName
<< " Perform actions before gracefully shutting down the service";
}

std::string extractJsonValue(const std::string& jsonLikeString,
const std::string& key) {
std::string result;
std::size_t start = jsonLikeString.find("\"" + key + "\"");

if (start != std::string::npos) {
start = jsonLikeString.find(":", start);
if (start != std::string::npos) {
start = jsonLikeString.find_first_of("\"", start);
if (start != std::string::npos) {
++start; // Skip the opening quote
std::size_t end = jsonLikeString.find("\"", start);
if (end != std::string::npos) {
result = jsonLikeString.substr(start, end - start);
}
}
}
}

return result;
}

void* Send(PlatformServiceImpl* service,
void* data,
uint64_t length,
uint64_t* output_length,
bool* invalid_state) {
SB_DCHECK(service);
SB_DCHECK(data);
SB_DCHECK(output_length);

char message[length + 1];
std::memcpy(message, data, length);
message[length] = '\0';

std::string response = "";

// TODO: Replace extractJsonValue function with a robust JSON parsing
// library for production use. The current implementation has limited
// capabilities that nested object isn't supported.
std::string method_name = extractJsonValue(message, "method");

if (method_name.length() == 0) {
SB_LOG(ERROR) << "Could not extract method value from the input JSON file:"
<< message;
}

// When method_name = getPartnerId, platform returns partner Id to get
// authenticated by YouTube app.
if (method_name == kGetPartnerIdMethod) {
// Populate Partner Id in the response.
// Partner Id will be used by YouTube app to authenticate platforms and
// retrieve specified topics for a platform.
// auto partner_id = "\"dummmy_partner_id\"";
response = FormatString("{\"partner_id\": \"%s\"}", kPartnerId);
}

// When method_name = recommend, platform processes data according to
// operation field.
if (method_name == kRecommendMethod) {
std::string operation = extractJsonValue(message, "operation");
if (operation.length() != 0) {
// When operation = upsert, platform parses recs_response field and
// insert/update data in local storage.
if (operation == kUpsertOp) {
std::string recs_response = extractJsonValue(message, "recs_response");

if (recs_response.length() != 0) {
// Store recommendations data in the local storage, such as local
// database or local filesystem.
SB_LOG(INFO) << "operation = " << operation
<< ", parse recs_response and store data locally";
} else {
SB_LOG(ERROR)
<< "Could not extract recsResponse from the input JSON file:"
<< message;
}
} else if (operation == kDeleteOp) {
// When operation = delete, platform delete YouTube data in local
// storage.
SB_LOG(INFO) << "operation = " << operation
<< ", data in local storage is deleted";
}
// Populate response with 200 to indicate data is processed
// successfully.
response = kSuccess;
} else {
// Populate response with error code if data isn't processed successfully.
response = kBadRequest;
}
}

*output_length = response.length();
auto ptr = malloc(*output_length);
response.copy(reinterpret_cast<char*>(ptr), response.length());
return ptr;
}

const CobaltPlatformServiceApi kGetPreappRecommendationServiceApi = {
kPreappRecommendationServiceName,
1, // API version that's implemented.
&Has,
&Open,
&Close,
&Send};

} // namespace

const void* GetPreappRecommendationServiceApi() {
return &kGetPreappRecommendationServiceApi;
}

} // namespace shared
} // namespace starboard
30 changes: 30 additions & 0 deletions starboard/linux/shared/pre_app_recommendation_service.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2024 The Cobalt Authors. 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.

#ifndef STARBOARD_LINUX_SHARED_PRE_APP_RECOMMENDATION_SERVICE_H_
#define STARBOARD_LINUX_SHARED_PRE_APP_RECOMMENDATION_SERVICE_H_

// Omit namespace 'linux' due to symbol name conflict with macro 'linux'
namespace starboard {
namespace shared {

const char* const kPreappRecommendationServiceName =
"com.google.youtube.tv.Recommendations";

const void* GetPreappRecommendationServiceApi();

} // namespace shared
} // namespace starboard

#endif // STARBOARD_LINUX_SHARED_PRE_APP_RECOMMENDATION_SERVICE_H_
Loading