-
Notifications
You must be signed in to change notification settings - Fork 193
tvOS: Enable Platform Service extension #9313
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,20 +49,36 @@ @implementation ObjCApplication { | |
| // Used for checking that certain methods are invoked from the UI thread (so | ||
| // that UIKit calls can be made directly, for example). | ||
| starboard::ThreadChecker _uiThreadChecker; | ||
|
|
||
| // The maximum number of frames per second a screen can render. | ||
| NSInteger _maximumFramesPerSecond; | ||
|
|
||
| // The last display refresh rate. | ||
| std::atomic<double> _lastDisplayRefreshRate; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this needs to be an atomic variable, especially if you use |
||
| } | ||
|
|
||
| @synthesize drmManager = _drmManager; | ||
| @synthesize playerManager = _playerManager; | ||
| @synthesize maximumFramesPerSecond = _maximumFramesPerSecond; | ||
|
|
||
| - (instancetype)init { | ||
| self = [super init]; | ||
| if (self) { | ||
| _drmManager = [[SBDDrmManager alloc] init]; | ||
| _playerManager = [[SBDPlayerManager alloc] init]; | ||
| _maximumFramesPerSecond = [[UIScreen mainScreen] maximumFramesPerSecond]; | ||
| } | ||
| return self; | ||
| } | ||
|
|
||
| - (void)updateLastDisplayRefreshRate:(double)refreshRate { | ||
| _lastDisplayRefreshRate = refreshRate; | ||
| } | ||
|
|
||
| - (double)displayRefreshRate { | ||
| return _lastDisplayRefreshRate; | ||
| } | ||
|
|
||
| - (void)setPlayerContainerView:(UIView*)view { | ||
| SB_CHECK(!_playerContainerView); | ||
| _playerContainerView = view; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Copyright 2026 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_TVOS_SHARED_PLATFORM_SERVICE_H_ | ||
| #define STARBOARD_TVOS_SHARED_PLATFORM_SERVICE_H_ | ||
|
|
||
| namespace starboard { | ||
|
|
||
| const void* GetPlatformServiceApiTvos(); | ||
|
|
||
| } // namespace starboard | ||
|
|
||
| #endif // STARBOARD_TVOS_SHARED_PLATFORM_SERVICE_H_ |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,121 @@ | ||||||||
| // Copyright 2026 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/tvos/shared/platform_service.h" | ||||||||
|
|
||||||||
| #import <UIKit/UIKit.h> | ||||||||
|
|
||||||||
| #include "starboard/common/log.h" | ||||||||
| #include "starboard/common/string.h" | ||||||||
| #include "starboard/extension/platform_service.h" | ||||||||
| #import "starboard/tvos/shared/starboard_application.h" | ||||||||
|
|
||||||||
| typedef struct CobaltExtensionPlatformServicePrivate { | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this needs to exist at all, |
||||||||
| void* Send(void* /*data*/, | ||||||||
| uint64_t /*length*/, | ||||||||
| uint64_t* output_length, | ||||||||
| bool* invalid_state) { | ||||||||
| if (!output_length || !invalid_state) { | ||||||||
| return NULL; | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/NULL/nullptr/ |
||||||||
| } | ||||||||
|
|
||||||||
| *invalid_state = false; | ||||||||
| std::string string = starboard::FormatString( | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| "MaximumFramesPerSecond:%ld;DisplayRefreshRate:%f;", | ||||||||
| SBDGetApplication().maximumFramesPerSecond, | ||||||||
| SBDGetApplication().displayRefreshRate); | ||||||||
|
|
||||||||
| *output_length = string.size() + 1; | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| char* output = (char*)malloc(*output_length); | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| if (!output) { | ||||||||
| *invalid_state = true; | ||||||||
| return NULL; | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| } | ||||||||
|
|
||||||||
| memcpy(output, string.c_str(), *output_length); | ||||||||
| return output; // caller must free() | ||||||||
| } | ||||||||
|
|
||||||||
| void* context; | ||||||||
| ReceiveMessageCallback receive_callback; | ||||||||
| } CobaltExtensionPlatformServicePrivate; | ||||||||
|
|
||||||||
| namespace starboard { | ||||||||
|
|
||||||||
| namespace { | ||||||||
|
|
||||||||
| const char* kClientLogInfoServiceName = "dev.cobalt.coat.clientloginfo"; | ||||||||
|
|
||||||||
| bool Has(const char* name) { | ||||||||
| if (!name) { | ||||||||
| return false; | ||||||||
| } | ||||||||
|
|
||||||||
| if (strcmp(name, kClientLogInfoServiceName) == 0) { | ||||||||
| return true; | ||||||||
| } | ||||||||
|
|
||||||||
| return false; | ||||||||
|
Comment on lines
+58
to
+69
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's take the opportunity and use more C++ here: constexpr std::string_view kClientLogInfoServiceName = "...";
bool Has(const char* name) {
if (!name) {
return false;
}
return name == kClientLogInfoServiceName;
} |
||||||||
| } | ||||||||
|
|
||||||||
| CobaltExtensionPlatformService Open(void* context, | ||||||||
| const char* name, | ||||||||
| ReceiveMessageCallback receive_callback) { | ||||||||
| SB_DCHECK(context); | ||||||||
|
|
||||||||
| if (!Has(name)) { | ||||||||
| SB_LOG(ERROR) << "Can't open Service " << name; | ||||||||
| return kCobaltExtensionPlatformServiceInvalid; | ||||||||
| } | ||||||||
|
|
||||||||
| CobaltExtensionPlatformService service = | ||||||||
| new CobaltExtensionPlatformServicePrivate({context, receive_callback}); | ||||||||
| return service; | ||||||||
| } | ||||||||
|
|
||||||||
| void Close(CobaltExtensionPlatformService service) { | ||||||||
| if (service == kCobaltExtensionPlatformServiceInvalid) { | ||||||||
| return; | ||||||||
| } | ||||||||
|
|
||||||||
| delete static_cast<CobaltExtensionPlatformServicePrivate*>(service); | ||||||||
| } | ||||||||
|
|
||||||||
| void* Send(CobaltExtensionPlatformService service, | ||||||||
| void* data, | ||||||||
| uint64_t length, | ||||||||
| uint64_t* output_length, | ||||||||
| bool* invalid_state) { | ||||||||
| SB_DCHECK(output_length); | ||||||||
| SB_DCHECK(invalid_state); | ||||||||
|
|
||||||||
| return static_cast<CobaltExtensionPlatformServicePrivate*>(service)->Send( | ||||||||
| data, length, output_length, invalid_state); | ||||||||
| } | ||||||||
|
|
||||||||
| const CobaltExtensionPlatformServiceApi kPlatformServiceApi = { | ||||||||
| kCobaltExtensionPlatformServiceName, | ||||||||
| 1, // API version that's implemented. | ||||||||
| &Has, | ||||||||
| &Open, | ||||||||
| &Close, | ||||||||
| &Send}; | ||||||||
|
|
||||||||
| } // namespace | ||||||||
|
|
||||||||
| const void* GetPlatformServiceApiTvos() { | ||||||||
| return &kPlatformServiceApi; | ||||||||
| } | ||||||||
|
|
||||||||
| } // namespace starboard | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't tried it out myself, but I think it should be possible to plug into https://source.chromium.org/chromium/chromium/src/+/main:components/viz/common/frame_sinks/external_begin_frame_source_ios.mm which already sets up a CADisplayLink, performs the
intervalcalculation and dispatches it.If you subclass
SimpleBeginFrameObserver, you should be able to retrieveintervalasframe_interval. If this works, you can just send this result toSBDGetApplication()and perform the division when the value is requested, as I don't think Kabuki will need this on every frame update.