blob: daebcb9ddd782ec30d3d8a9796de4e056ac8c0af [file] [edit]
//
// Copyright 2020 Comcast Cable Communications Management, LLC
//
// 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.
//
// SPDX-License-Identifier: Apache-2.0//
// Copyright 2016 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 "third_party/starboard/rdk/shared/application_rdk.h"
#include "starboard/common/log.h"
#include "starboard/event.h"
#include "starboard/speech_synthesis.h"
#include "starboard/shared/starboard/audio_sink/audio_sink_internal.h"
#include "starboard/shared/starboard/media/key_system_supportability_cache.h"
#include "starboard/shared/starboard/media/mime_supportability_cache.h"
#include "third_party/starboard/rdk/shared/window/window_internal.h"
#include "third_party/starboard/rdk/shared/log_override.h"
#include <fcntl.h>
#include <poll.h>
#include <cstring>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <malloc.h>
namespace third_party {
namespace starboard {
namespace rdk {
namespace shared {
namespace libcobalt_api {
void Initialize();
void Teardown();
}
namespace player {
void ForceStop();
} // namespace player
EssTerminateListener Application::terminateListener = {
//terminated
[](void* data) { reinterpret_cast<Application*>(data)->OnTerminated(); }
};
EssKeyListener Application::keyListener = {
// keyPressed
[](void* data, unsigned int key) { reinterpret_cast<Application*>(data)->OnKeyPressed(key); },
// keyReleased
[](void* data, unsigned int key) { reinterpret_cast<Application*>(data)->OnKeyReleased(key); },
// keyRepeat
[](void* data, unsigned int key) { reinterpret_cast<Application*>(data)->OnKeyPressed(key); }
};
EssSettingsListener Application::settingsListener = {
// displaySize
[](void *data, int width, int height ) { reinterpret_cast<Application*>(data)->OnDisplaySize(width, height); },
// displaySafeArea
nullptr
};
const SbTime kEssRunLoopPeriod = 16666; // microseconds
static void setTimerInterval(int fd, SbTime time) {
struct itimerspec timeout;
timeout.it_value.tv_sec = time / kSbTimeSecond;
timeout.it_value.tv_nsec = ( time % kSbTimeSecond ) * kSbTimeNanosecondsPerMicrosecond;
timeout.it_interval.tv_sec = timeout.it_value.tv_sec;
timeout.it_interval.tv_nsec = timeout.it_value.tv_nsec;
int rc = timerfd_settime(fd, 0, &timeout, NULL);
if (rc == -1) {
SB_LOG(ERROR) << "Failed to set timer interval, error: " << errno << " ("<< strerror(errno) << ')';
}
}
#if SB_API_VERSION >= 15
Application::Application(SbEventHandleCallback sb_event_handle_callback)
: QueueApplication(sb_event_handle_callback)
#else
Application::Application()
: QueueApplication()
#endif
, input_handler_(new EssInput)
, hang_monitor_(new HangMonitor("Application")) {
essos_context_recycle_ = !!getenv("COBALT_ESSOS_CONTEXT_DESTROY");
BuildEssosContext();
}
Application::~Application() {
EssContextDestroy(ctx_);
}
void Application::Initialize() {
wakeup_fd_ = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
if ( wakeup_fd_ == -1 ) {
SB_LOG(ERROR) << "Failed to create eventfd, error: " << errno << " (" << strerror(errno) << ')';
}
ess_timer_fd_ = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
if ( ess_timer_fd_ == -1 ) {
SB_LOG(ERROR) << "Failed to create timerfd, error: " << errno << " (" << strerror(errno) << ')';
} else {
setTimerInterval(ess_timer_fd_, kEssRunLoopPeriod);
}
monitor_timer_fd_ = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
if ( monitor_timer_fd_ == -1 ) {
SB_LOG(ERROR) << "Failed to create timerfd, error: " << errno << " (" << strerror(errno) << ')';
hang_monitor_.reset();
} else {
setTimerInterval(monitor_timer_fd_, hang_monitor_->GetResetInterval());
}
SbAudioSinkPrivate::Initialize();
libcobalt_api::Initialize();
using ::starboard::shared::starboard::media::KeySystemSupportabilityCache;
using ::starboard::shared::starboard::media::MimeSupportabilityCache;
MimeSupportabilityCache::GetInstance()->SetCacheEnabled(true);
KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(true);
ScheduleMemoryUsageCheck();
}
void Application::Teardown() {
SbAudioSinkPrivate::TearDown();
libcobalt_api::Teardown();
TeardownJSONRPCLink();
close(ess_timer_fd_);
close(wakeup_fd_);
close(monitor_timer_fd_);
ess_timer_fd_ = wakeup_fd_ = monitor_timer_fd_ = -1;
}
bool Application::MayHaveSystemEvents() {
return true;
}
::starboard::shared::starboard::Application::Event*
Application::PollNextSystemEvent() {
SbTime now = SbTimeGetMonotonicNow();
if ((now - ess_loop_last_ts_) > kEssRunLoopPeriod) {
ess_loop_last_ts_ = now;
EssContextRunEventLoopOnce( ctx_ );
}
return NULL;
}
::starboard::shared::starboard::Application::Event*
Application::WaitForSystemEventWithTimeout(SbTime time) {
struct timespec timeout;
struct pollfd fds[3];
int fds_sz = 0;
int rc = 0;
if ( !(ess_timer_fd_ < 0) ) {
fds[fds_sz].fd = ess_timer_fd_;
fds[fds_sz].events = POLLIN;
fds[fds_sz].revents = 0;
++fds_sz;
}
if ( !(wakeup_fd_ < 0) ) {
fds[fds_sz].fd = wakeup_fd_;
fds[fds_sz].events = POLLIN;
fds[fds_sz].revents = 0;
++fds_sz;
}
if ( !(monitor_timer_fd_ < 0) ) {
fds[fds_sz].fd = monitor_timer_fd_;
fds[fds_sz].events = POLLIN;
fds[fds_sz].revents = 0;
++fds_sz;
}
if ( fds_sz != 0 ) {
timeout.tv_sec = time / kSbTimeSecond;
timeout.tv_nsec = (time % kSbTimeSecond) * kSbTimeNanosecondsPerMicrosecond;
rc = ppoll(fds, fds_sz, &timeout, NULL);
}
if ( rc > 0 ) {
for (int i = 0; i < fds_sz; ++i) {
if ( (fds[i].revents & POLLIN) != POLLIN )
continue;
// Ack timer or wakeup event
uint64_t tmp;
read(fds[i].fd, &tmp, sizeof(uint64_t));
if ( fds[i].fd == monitor_timer_fd_ ) {
hang_monitor_->Reset();
}
}
}
return NULL;
}
void Application::WakeSystemEventWait() {
uint64_t u = 1;
write(wakeup_fd_, &u, sizeof(uint64_t));
}
SbWindow Application::CreateSbWindow(const SbWindowOptions* options) {
SB_DCHECK(window_ == nullptr);
if (window_ != nullptr)
return kSbWindowInvalid;
MaterializeNativeWindow();
window_ = new SbWindowPrivate(options);
return window_;
}
bool Application::DestroySbWindow(SbWindow window) {
if (!SbWindowIsValid(window))
return false;
window_ = nullptr;
delete window;
DestroyNativeWindow();
return true;
}
void Application::InjectInputEvent(SbInputData* data) {
if (native_window_ == 0) {
Application::DeleteDestructor<SbInputData>(data);
return;
}
data->window = window_;
Inject(new Event(kSbEventTypeInput, data,
&Application::DeleteDestructor<SbInputData>));
}
void Application::Inject(Event* e) {
if (e && e->event && e->event->type == kSbEventTypeFreeze) {
player::ForceStop();
}
QueueApplication::Inject(e);
}
void Application::OnSuspend() {
SbSpeechSynthesisCancel();
DestroyNativeWindow();
TeardownJSONRPCLink();
setTimerInterval(ess_timer_fd_, kSbTimeSecond);
}
void Application::OnResume() {
if ( essos_context_recycle_ )
BuildEssosContext();
setTimerInterval(ess_timer_fd_, kEssRunLoopPeriod);
MaterializeNativeWindow();
}
void Application::OnTerminated() {
Stop(0);
}
void Application::OnKeyPressed(unsigned int key) {
input_handler_->OnKeyPressed(key);
}
void Application::OnKeyReleased(unsigned int key) {
input_handler_->OnKeyReleased(key);
}
void Application::OnDisplaySize(int width, int height) {
if (window_width_ == width && window_height_ == height) {
resize_pending_ = false;
return;
}
SB_DCHECK(native_window_ == 0);
resize_pending_ = true;
}
void Application::MaterializeNativeWindow() {
if (native_window_ != 0)
return;
bool error = false;
if ( !EssContextGetDisplaySize(ctx_, &window_width_, &window_height_) ) {
error = true;
}
if ( resize_pending_ ) {
EssContextResizeWindow(ctx_, window_width_, window_height_);
resize_pending_ = false;
}
if ( !EssContextCreateNativeWindow(ctx_, window_width_, window_height_, &native_window_) ) {
error = true;
}
else if ( !EssContextStart(ctx_) ) {
error = true;
}
if ( error ) {
const char *detail = EssContextGetLastErrorDetail(ctx_);
SB_LOG(ERROR) << "Essos error: '" << detail << '\'';
FatalError();
}
}
void Application::DestroyNativeWindow() {
if (native_window_ == 0)
return;
if ( !EssContextDestroyNativeWindow(ctx_, native_window_) ) {
const char *detail = EssContextGetLastErrorDetail(ctx_);
SB_LOG(ERROR) << "Essos error: '" << detail << '\'';
}
native_window_ = 0;
if ( essos_context_recycle_ ) {
EssContextDestroy(ctx_);
ctx_ = NULL;
}
else
EssContextStop(ctx_);
}
void Application::DisplayInfoChanged() {
if (state() != kStateStarted)
return;
SbWindowSize window_size;
SbWindowGetSize(window_, &window_size);
auto *data = new SbEventWindowSizeChangedData();
data->size = window_size;
data->window = window_;
WindowSizeChanged(data, &Application::DeleteDestructor<SbEventWindowSizeChangedData>);
}
void Application::BuildEssosContext() {
bool error = false;
ctx_ = EssContextCreate();
if ( !EssContextInit(ctx_) ) {
error = true;
}
else if ( !EssContextSetTerminateListener(ctx_, this, &terminateListener) ) {
error = true;
}
else if ( !EssContextSetKeyListener(ctx_, this, &keyListener) ) {
error = true;
}
else if ( !EssContextSetSettingsListener(ctx_, this, &settingsListener) ) {
error = true;
}
if ( error ) {
const char *detail = EssContextGetLastErrorDetail(ctx_);
SB_LOG(ERROR) << "Essos error: '" << detail << '\'';
FatalError();
}
}
void Application::FatalError() {
SB_LOG(ERROR) << "Exiting due to fatal error.";
ess_loop_last_ts_ = kSbTimeMax; // Stop Essos run loop
SbEventSchedule([](void* data) {
Application::Get()->Stop(0);
}, nullptr, 0);
}
void Application::ReleaseMemory() {
Inject(new Event(kSbEventTypeLowMemory, NULL, [](void*) {
malloc_trim(0);
}));
}
void Application::ScheduleMemoryUsageCheck(SbTime delay) {
SbEventSchedule([](void* data) {
SbTime back_off_timeout = Application::Get()->CheckMemoryUsage();
if (back_off_timeout && back_off_timeout != kSbTimeMax)
Application::Get()->ScheduleMemoryUsageCheck(back_off_timeout);
}, nullptr, delay);
}
SbTime Application::CheckMemoryUsage() {
static const int64_t kCPUMemoryPressureLimit = ([]() -> int64_t {
const char* env = std::getenv("COBALT_CPU_MEM_PRESSURE_IN_MB");
int64_t limit_in_mb = SB_INT64_C(400);
if( env ) {
int64_t t = strtol(env, nullptr, 0);
if ( t >= 0 )
limit_in_mb = t;
}
int64_t total_in_bytes = SbSystemGetTotalCPUMemory();
return std::min(total_in_bytes, limit_in_mb * 1024 * 1024);
})();
if (!kCPUMemoryPressureLimit)
return kSbTimeMax;
int64_t usage_in_bytes = SbSystemGetUsedCPUMemory();
if (kCPUMemoryPressureLimit < usage_in_bytes) {
SB_LOG(INFO) << "Triggering memory pressure event. Current CPU mem. usage: "
<< uint64_t(usage_in_bytes / 1024) << " kb, pressure limit: "
<< uint64_t(kCPUMemoryPressureLimit / 1024) << " kb.";
ReleaseMemory();
return 5 * kSbTimeSecond;
}
return kSbTimeSecond;
}
void Application::InjectAccessibilitySettingsChanged() {
Inject(new Event(kSbEventTypeAccessibilitySettingsChanged, NULL, NULL));
}
void Application::InjectAccessibilityCaptionSettingsChanged() {
Inject(new Event(kSbEventTypeAccessibilityCaptionSettingsChanged, NULL, NULL));
}
void Application::InjectAccessibilityTextToSpeechSettingsChanged() {
Inject(new Event(kSbEventTypeAccessibilityTextToSpeechSettingsChanged, NULL, NULL));
}
} // namespace shared
} // namespace rdk
} // namespace starboard
} // namespace third_party