-
Notifications
You must be signed in to change notification settings - Fork 121
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cherry pick PR #3277: pre-app recommendation cobalt platform service …
…reference code (#3331) Refer to the original PR: #3277 b/325133487 b/342046135 Co-authored-by: Yunzi Zhang <[email protected]>
- Loading branch information
1 parent
9adfbb4
commit e59dd58
Showing
5 changed files
with
353 additions
and
1 deletion.
There are no files selected for viewing
125 changes: 125 additions & 0 deletions
125
cobalt/demos/content/pre-app-recommendation-demo/pre-app-recommendation-demo.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
193 changes: 193 additions & 0 deletions
193
starboard/linux/shared/pre_app_recommendation_service.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |