Skip to content

Commit

Permalink
Cherry pick PR #3277: pre-app recommendation cobalt platform service …
Browse files Browse the repository at this point in the history
…reference code (#3331)

Refer to the original PR: #3277

b/325133487
b/342046135

Co-authored-by: Yunzi Zhang <[email protected]>
  • Loading branch information
cobalt-github-releaser-bot and yzzhang11 committed May 23, 2024
1 parent 9adfbb4 commit e59dd58
Show file tree
Hide file tree
Showing 5 changed files with 353 additions and 1 deletion.
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_

0 comments on commit e59dd58

Please sign in to comment.