Skip to content
Open
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
38 changes: 38 additions & 0 deletions cobalt/shell/browser/shell_platform_delegate_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ @interface ContentShellWindowDelegate : UIViewController <UITextFieldDelegate> {
@property(nonatomic, strong) UIView* contentView;
// Manages tracing and tracing state.
@property(nonatomic, strong) TracingHandler* tracingHandler;
#if BUILDFLAG(IS_IOS_TVOS)
// Used to calculate display refresh rate.
@property(nonatomic, strong) CADisplayLink* displayLink;
#endif

+ (UIColor*)backgroundColorDefault;
+ (UIColor*)backgroundColorTracing;
Expand Down Expand Up @@ -183,6 +187,29 @@ - (void)pressesEnded:(NSSet<UIPress*>*)presses
[super pressesEnded:nonMenuPresses withEvent:event];
}
}

- (void)update:(CADisplayLink*)sender {
Copy link
Collaborator

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 interval calculation and dispatches it.

If you subclass SimpleBeginFrameObserver, you should be able to retrieve interval as frame_interval. If this works, you can just send this result to SBDGetApplication() and perform the division when the value is requested, as I don't think Kabuki will need this on every frame update.

// Calculate the actual frame rate.
double interval = sender.targetTimestamp - sender.timestamp;
if (interval > 0) {
double lastDisplayRefreshRate = 1.0 / interval;
[SBDGetApplication() updateLastDisplayRefreshRate:lastDisplayRefreshRate];
}
}

- (void)startDisplayLink {
if (!self.displayLink) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self
selector:@selector(update:)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
}

- (void)stopDisplayLink {
[self.displayLink invalidate];
self.displayLink = nil;
}
#endif // BUILDFLAG(IS_IOS_TVOS)

- (void)viewDidLoad {
Expand Down Expand Up @@ -286,8 +313,19 @@ - (void)viewDidLoad {
// Once the splash screen web contents are created, the corresponding UIView
// will be added to `_contentView`.
_shell->LoadSplashScreenWebContents();

#if BUILDFLAG(IS_IOS_TVOS)
[self startDisplayLink];
#endif
}

#if BUILDFLAG(IS_IOS_TVOS)
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self stopDisplayLink];
}
#endif

- (id)initWithShell:(content::Shell*)shell {
if ((self = [super init])) {
_shell = shell;
Expand Down
2 changes: 2 additions & 0 deletions starboard/tvos/shared/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ static_library("starboard_platform") {
"media_is_video_supported.mm",
"observer_registry.cc",
"observer_registry.h",
"platform_service.h",
"platform_service.mm",
"run_in_background_thread_and_wait.cc",
"run_in_background_thread_and_wait.h",
"starboard_application.h",
Expand Down
16 changes: 16 additions & 0 deletions starboard/tvos/shared/application_darwin.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 PostTask() from cobalt shell to make the writes sequential.

}

@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;
Expand Down
24 changes: 24 additions & 0 deletions starboard/tvos/shared/platform_service.h
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_
121 changes: 121 additions & 0 deletions starboard/tvos/shared/platform_service.mm
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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs to exist at all, context and receive_callback are not used anywhere, so Send()'s implementation could be merged directly into Send() below.

void* Send(void* /*data*/,
uint64_t /*length*/,
uint64_t* output_length,
bool* invalid_state) {
if (!output_length || !invalid_state) {
return NULL;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/NULL/nullptr/

}

*invalid_state = false;
std::string string = starboard::FormatString(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::string string = starboard::FormatString(
const std::string string = starboard::FormatString(

"MaximumFramesPerSecond:%ld;DisplayRefreshRate:%f;",
SBDGetApplication().maximumFramesPerSecond,
SBDGetApplication().displayRefreshRate);

*output_length = string.size() + 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*output_length = string.size() + 1;
// Other extensions do not include a NUL-terminating byte. This is done here for compatibility with C25.
*output_length = string.size() + 1;

char* output = (char*)malloc(*output_length);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
char* output = (char*)malloc(*output_length);
auto* output = malloc(*output_length);

if (!output) {
*invalid_state = true;
return NULL;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return NULL;
return nullptr;

}

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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
12 changes: 12 additions & 0 deletions starboard/tvos/shared/starboard_application.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ id<SBDStarboardApplication> SBDGetApplication(void);
*/
@property(nonatomic, readonly) SBDPlayerManager* playerManager;

/**
* @brief Returns display refresh rate.
*/
@property(nonatomic, readonly) double displayRefreshRate;

/**
* @brief Returns the maximum number of frames per second a screen can render.
*/
@property(nonatomic, readonly) NSInteger maximumFramesPerSecond;

// Sets the UIView to which player views will be added to.
- (void)setPlayerContainerView:(UIView*)view;

Expand All @@ -78,6 +88,8 @@ id<SBDStarboardApplication> SBDGetApplication(void);
// suspending the application.
- (void)registerMenuPressEnded:(UIPress*)press
pressesEvent:(UIPressesEvent*)pressesEvent;

- (void)updateLastDisplayRefreshRate:(double)lastDisplayRefreshRate;
@end

NS_ASSUME_NONNULL_END
Expand Down
4 changes: 2 additions & 2 deletions starboard/tvos/shared/system_get_extensions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "starboard/tvos/shared/accessibility_extension.h"
#include "starboard/tvos/shared/crash_handler.h"
#include "starboard/tvos/shared/media/player_configuration.h"
#include "starboard/tvos/shared/platform_service.h"
#include "starboard/tvos/shared/uikit_media_session_client.h"

const void* SbSystemGetExtension(const char* name) {
Expand All @@ -48,8 +49,7 @@ const void* SbSystemGetExtension(const char* name) {
return nullptr;
}
if (strcmp(name, kCobaltExtensionPlatformServiceName) == 0) {
SB_LOG(INFO) << "The platform service extension is not supported on tvOS";
return nullptr;
return starboard::GetPlatformServiceApiTvos();
}
if (strcmp(name, kStarboardExtensionIfaName) == 0) {
SB_LOG(INFO) << "IFA is not supported via Starboard.";
Expand Down
Loading