mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-04 02:28:36 +00:00
perf: warm desktop player controls loading
This commit is contained in:
parent
5b8a4aff9b
commit
bc75392872
4 changed files with 344 additions and 62 deletions
|
|
@ -2,7 +2,6 @@ package com.nuvio.app.features.player.desktop
|
|||
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.util.Base64
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
internal fun interface NativePlayerEventSink {
|
||||
|
|
@ -22,7 +21,7 @@ internal object NativePlayerBridge {
|
|||
headerLines: Array<String>,
|
||||
playWhenReady: Boolean,
|
||||
initialPositionMs: Long,
|
||||
controlsHtml: String,
|
||||
controlsPageUrl: String,
|
||||
eventSink: NativePlayerEventSink,
|
||||
): Long
|
||||
|
||||
|
|
@ -66,24 +65,35 @@ internal object NativePlayerBridge {
|
|||
fontSize: Float,
|
||||
subPos: Int,
|
||||
)
|
||||
external fun warmupWebView2(controlsPageUrl: String): Boolean
|
||||
external fun shutdownWebView2Warmup()
|
||||
|
||||
val controlsHtml: String by lazy {
|
||||
val resource = "/player-ui/controls.html"
|
||||
val input = NativePlayerBridge::class.java.getResourceAsStream(resource)
|
||||
?: error("Missing native player controls resource: $resource")
|
||||
input.bufferedReader().use { it.readText() }
|
||||
.replace("/* __NUVIO_PLAYER_FONT_FACES__ */", nativePlayerFontFaces())
|
||||
}
|
||||
val controlsPageUrl: String by lazy { controlsPageAssets.url }
|
||||
private val controlsPageAssets: ControlsPageAssets by lazy { exportControlsPageAssets() }
|
||||
|
||||
fun preloadAsync() {
|
||||
if (!preloadStarted.compareAndSet(false, true)) return
|
||||
Thread {
|
||||
runCatching { controlsHtml }
|
||||
val controlsPage = runCatching { controlsPageAssets }
|
||||
.getOrNull()
|
||||
?: return@Thread
|
||||
if (DesktopHostOs.current == DesktopHostOs.WINDOWS) {
|
||||
runCatching { warmupWebView2(controlsPage.url) }
|
||||
}
|
||||
}.apply {
|
||||
name = "nuvio-native-player-preload"
|
||||
isDaemon = true
|
||||
start()
|
||||
}
|
||||
if (DesktopHostOs.current == DesktopHostOs.WINDOWS) {
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
Thread {
|
||||
runCatching { shutdownWebView2Warmup() }
|
||||
}.apply {
|
||||
name = "nuvio-webview2-warmup-shutdown"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadNativeLibrary() {
|
||||
|
|
@ -184,38 +194,77 @@ internal object NativePlayerBridge {
|
|||
DesktopHostOs.UNKNOWN -> "player_bridge"
|
||||
}
|
||||
|
||||
private fun nativePlayerFontFaces(): String =
|
||||
listOfNotNull(
|
||||
nativePlayerFontFace(
|
||||
fileName = "jetbrains_sans_regular.ttf",
|
||||
weight = "400",
|
||||
),
|
||||
nativePlayerFontFace(
|
||||
fileName = "jetbrains_sans_semibold.ttf",
|
||||
weight = "600",
|
||||
),
|
||||
nativePlayerFontFace(
|
||||
fileName = "jetbrains_sans_bold.ttf",
|
||||
weight = "700 900",
|
||||
),
|
||||
).joinToString(separator = "\n")
|
||||
private fun exportControlsPageAssets(): ControlsPageAssets {
|
||||
val root = File(System.getProperty("java.io.tmpdir"), "nuvio-player-ui").apply { mkdirs() }
|
||||
val fontsDir = root.resolve("fonts").apply { mkdirs() }
|
||||
val htmlResource = "/player-ui/controls.html"
|
||||
val html = NativePlayerBridge::class.java.getResourceAsStream(htmlResource)
|
||||
?.bufferedReader()
|
||||
?.use { it.readText() }
|
||||
?: error("Missing native player controls resource: $htmlResource")
|
||||
val resolvedHtml = html.replace("/* __NUVIO_PLAYER_FONT_FACES__ */", nativePlayerFontFaces())
|
||||
val htmlFile = root.resolve("controls.html")
|
||||
writeTextIfChanged(htmlFile, resolvedHtml)
|
||||
copyResourceIfChanged(
|
||||
resource = "/composeResources/nuvio.composeapp.generated.resources/font/jetbrains_sans_regular.ttf",
|
||||
target = fontsDir.resolve("jetbrains_sans_regular.ttf"),
|
||||
)
|
||||
copyResourceIfChanged(
|
||||
resource = "/composeResources/nuvio.composeapp.generated.resources/font/jetbrains_sans_semibold.ttf",
|
||||
target = fontsDir.resolve("jetbrains_sans_semibold.ttf"),
|
||||
)
|
||||
copyResourceIfChanged(
|
||||
resource = "/composeResources/nuvio.composeapp.generated.resources/font/jetbrains_sans_bold.ttf",
|
||||
target = fontsDir.resolve("jetbrains_sans_bold.ttf"),
|
||||
)
|
||||
return ControlsPageAssets(
|
||||
url = htmlFile.toURI().toASCIIString(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun nativePlayerFontFace(fileName: String, weight: String): String? {
|
||||
val resource = "/composeResources/nuvio.composeapp.generated.resources/font/$fileName"
|
||||
val bytes = NativePlayerBridge::class.java.getResourceAsStream(resource)
|
||||
?.use { it.readBytes() }
|
||||
?: return null
|
||||
val encoded = Base64.getEncoder().encodeToString(bytes)
|
||||
return """
|
||||
private fun nativePlayerFontFaces(): String =
|
||||
"""
|
||||
@font-face {
|
||||
font-family: "Nuvio JetBrains Sans";
|
||||
src: url("data:font/ttf;base64,$encoded") format("truetype");
|
||||
font-weight: $weight;
|
||||
src: url("fonts/jetbrains_sans_regular.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Nuvio JetBrains Sans";
|
||||
src: url("fonts/jetbrains_sans_semibold.ttf") format("truetype");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Nuvio JetBrains Sans";
|
||||
src: url("fonts/jetbrains_sans_bold.ttf") format("truetype");
|
||||
font-weight: 700 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
private fun writeTextIfChanged(target: File, text: String) {
|
||||
val bytes = text.toByteArray(Charsets.UTF_8)
|
||||
if (target.exists() && target.readBytes().contentEquals(bytes)) return
|
||||
target.writeBytes(bytes)
|
||||
}
|
||||
|
||||
private fun copyResourceIfChanged(resource: String, target: File) {
|
||||
val bytes = NativePlayerBridge::class.java.getResourceAsStream(resource)
|
||||
?.use { it.readBytes() }
|
||||
?: error("Missing native player controls resource: $resource")
|
||||
if (target.exists() && target.readBytes().contentEquals(bytes)) return
|
||||
Files.createDirectories(target.parentFile.toPath())
|
||||
target.writeBytes(bytes)
|
||||
}
|
||||
|
||||
private data class ControlsPageAssets(
|
||||
val url: String,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun preloadNativePlayerBridgeAsync() {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ internal class NativePlayerController(
|
|||
headerLines = pending.headerLines.toTypedArray(),
|
||||
playWhenReady = pending.playWhenReady,
|
||||
initialPositionMs = pending.initialPositionMs,
|
||||
controlsHtml = NativePlayerBridge.controlsHtml,
|
||||
controlsPageUrl = NativePlayerBridge.controlsPageUrl,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
if (handle == 0L) error("Native player did not return a handle.")
|
||||
|
|
|
|||
|
|
@ -86,10 +86,10 @@ void mpv_set_wakeup_callback(mpv_handle *ctx, void (*cb)(void *d), void *d);
|
|||
@interface MpvWebPlayer : NSObject
|
||||
- (instancetype)initWithHostView:(NSView *)hostView
|
||||
sourceUrl:(NSString *)sourceUrl
|
||||
headerLines:(NSArray<NSString *> *)headerLines
|
||||
playWhenReady:(BOOL)playWhenReady
|
||||
initialPositionMs:(long long)initialPositionMs
|
||||
controlsHtml:(NSString *)controlsHtml
|
||||
headerLines:(NSArray<NSString *> *)headerLines
|
||||
playWhenReady:(BOOL)playWhenReady
|
||||
initialPositionMs:(long long)initialPositionMs
|
||||
controlsUrl:(NSString *)controlsUrl
|
||||
javaVm:(JavaVM *)javaVm
|
||||
eventSink:(jobject)eventSink
|
||||
eventMethod:(jmethodID)eventMethod;
|
||||
|
|
@ -750,10 +750,10 @@ static NSString *redactUrlsInText(NSString *text) {
|
|||
|
||||
- (instancetype)initWithHostView:(NSView *)hostView
|
||||
sourceUrl:(NSString *)sourceUrl
|
||||
headerLines:(NSArray<NSString *> *)headerLines
|
||||
playWhenReady:(BOOL)playWhenReady
|
||||
initialPositionMs:(long long)initialPositionMs
|
||||
controlsHtml:(NSString *)controlsHtml
|
||||
headerLines:(NSArray<NSString *> *)headerLines
|
||||
playWhenReady:(BOOL)playWhenReady
|
||||
initialPositionMs:(long long)initialPositionMs
|
||||
controlsUrl:(NSString *)controlsUrl
|
||||
javaVm:(JavaVM *)javaVm
|
||||
eventSink:(jobject)eventSink
|
||||
eventMethod:(jmethodID)eventMethod {
|
||||
|
|
@ -790,7 +790,17 @@ static NSString *redactUrlsInText(NSString *text) {
|
|||
_webView.wantsLayer = YES;
|
||||
[_webView setValue:@NO forKey:@"drawsBackground"];
|
||||
[_hostView addSubview:_webView positioned:NSWindowAbove relativeTo:_videoView];
|
||||
[_webView loadHTMLString:controlsHtml baseURL:nil];
|
||||
NSURL *controlsURL = [NSURL URLWithString:controlsUrl ?: @""];
|
||||
if (!controlsURL) {
|
||||
@throw [NSException exceptionWithName:@"PlayerBridgeError"
|
||||
reason:@"Invalid native player controls URL."
|
||||
userInfo:nil];
|
||||
}
|
||||
if (controlsURL.isFileURL) {
|
||||
[_webView loadFileURL:controlsURL allowingReadAccessToURL:[controlsURL URLByDeletingLastPathComponent]];
|
||||
} else {
|
||||
[_webView loadRequest:[NSURLRequest requestWithURL:controlsURL]];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(hostViewFrameDidChange:)
|
||||
name:NSViewFrameDidChangeNotification
|
||||
|
|
@ -1994,7 +2004,7 @@ Java_com_nuvio_app_features_player_desktop_NativePlayerBridge_create(
|
|||
jobjectArray headerLines,
|
||||
jboolean playWhenReady,
|
||||
jlong initialPositionMs,
|
||||
jstring controlsHtml,
|
||||
jstring controlsPageUrl,
|
||||
jobject eventSink
|
||||
) {
|
||||
NSView *hostView = (__bridge NSView *)(void *)(intptr_t)hostViewPtr;
|
||||
|
|
@ -2022,7 +2032,7 @@ Java_com_nuvio_app_features_player_desktop_NativePlayerBridge_create(
|
|||
}
|
||||
|
||||
std::string source = jstringToString(env, sourceUrl);
|
||||
std::string controls = jstringToString(env, controlsHtml);
|
||||
std::string controls = jstringToString(env, controlsPageUrl);
|
||||
NSArray<NSString *> *headers = jstringArrayToNSArray(env, headerLines);
|
||||
__block MpvWebPlayer *player = nil;
|
||||
__block NSString *error = nil;
|
||||
|
|
@ -2034,7 +2044,7 @@ Java_com_nuvio_app_features_player_desktop_NativePlayerBridge_create(
|
|||
headerLines:headers
|
||||
playWhenReady:playWhenReady == JNI_TRUE
|
||||
initialPositionMs:initialPositionMs
|
||||
controlsHtml:[NSString stringWithUTF8String:controls.c_str()]
|
||||
controlsUrl:[NSString stringWithUTF8String:controls.c_str()]
|
||||
javaVm:javaVm
|
||||
eventSink:eventSinkRef
|
||||
eventMethod:eventMethod];
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <condition_variable>
|
||||
#include <cctype>
|
||||
|
|
@ -369,6 +370,217 @@ void registerWindowClasses() {
|
|||
});
|
||||
}
|
||||
|
||||
std::mutex gWebView2WarmupMutex;
|
||||
std::condition_variable gWebView2WarmupCv;
|
||||
std::thread gWebView2WarmupThread;
|
||||
DWORD gWebView2WarmupThreadId = 0;
|
||||
bool gWebView2WarmupStarted = false;
|
||||
bool gWebView2WarmupReady = false;
|
||||
bool gWebView2WarmupSucceeded = false;
|
||||
|
||||
void notifyWebView2WarmupReady(bool succeeded) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gWebView2WarmupMutex);
|
||||
if (!gWebView2WarmupReady) {
|
||||
gWebView2WarmupReady = true;
|
||||
gWebView2WarmupSucceeded = succeeded;
|
||||
}
|
||||
}
|
||||
gWebView2WarmupCv.notify_all();
|
||||
}
|
||||
|
||||
void runWebView2WarmupThread(std::string controlsUrl) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gWebView2WarmupMutex);
|
||||
gWebView2WarmupThreadId = GetCurrentThreadId();
|
||||
}
|
||||
gWebView2WarmupCv.notify_all();
|
||||
|
||||
MSG queueProbe = {};
|
||||
PeekMessageW(&queueProbe, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
|
||||
|
||||
bool didOleInitialize = false;
|
||||
ComPtr<ICoreWebView2Environment> environment;
|
||||
ComPtr<ICoreWebView2Controller> controller;
|
||||
ComPtr<ICoreWebView2> webView;
|
||||
EventRegistrationToken messageToken = {};
|
||||
EventRegistrationToken navigationToken = {};
|
||||
|
||||
HRESULT oleResult = OleInitialize(nullptr);
|
||||
didOleInitialize = SUCCEEDED(oleResult);
|
||||
if (FAILED(oleResult)) {
|
||||
notifyWebView2WarmupReady(false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::wstring userDataDir = tempUserDataDirectory();
|
||||
HRESULT envCallResult = CreateCoreWebView2EnvironmentWithOptions(
|
||||
nullptr,
|
||||
userDataDir.c_str(),
|
||||
nullptr,
|
||||
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
|
||||
[&](HRESULT envResult, ICoreWebView2Environment *createdEnvironment) -> HRESULT {
|
||||
if (FAILED(envResult) || !createdEnvironment) {
|
||||
notifyWebView2WarmupReady(false);
|
||||
PostQuitMessage(0);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
environment = createdEnvironment;
|
||||
HRESULT controllerCallResult = createdEnvironment->CreateCoreWebView2Controller(
|
||||
HWND_MESSAGE,
|
||||
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
|
||||
[&](HRESULT controllerResult, ICoreWebView2Controller *createdController) -> HRESULT {
|
||||
if (FAILED(controllerResult) || !createdController) {
|
||||
notifyWebView2WarmupReady(false);
|
||||
PostQuitMessage(0);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
controller = createdController;
|
||||
controller->put_IsVisible(FALSE);
|
||||
createdController->get_CoreWebView2(&webView);
|
||||
if (!webView) {
|
||||
notifyWebView2WarmupReady(false);
|
||||
PostQuitMessage(0);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
webView->add_WebMessageReceived(
|
||||
Callback<ICoreWebView2WebMessageReceivedEventHandler>(
|
||||
[&](ICoreWebView2 *, ICoreWebView2WebMessageReceivedEventArgs *args) -> HRESULT {
|
||||
if (!args) return S_OK;
|
||||
PWSTR messageJson = nullptr;
|
||||
if (SUCCEEDED(args->get_WebMessageAsJson(&messageJson)) && messageJson) {
|
||||
std::wstring message(messageJson);
|
||||
CoTaskMemFree(messageJson);
|
||||
if (message.find(L"controlsReady") != std::wstring::npos) {
|
||||
notifyWebView2WarmupReady(true);
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
).Get(),
|
||||
&messageToken
|
||||
);
|
||||
|
||||
webView->add_NavigationCompleted(
|
||||
Callback<ICoreWebView2NavigationCompletedEventHandler>(
|
||||
[&](ICoreWebView2 *, ICoreWebView2NavigationCompletedEventArgs *args) -> HRESULT {
|
||||
BOOL navigationSucceeded = FALSE;
|
||||
if (args) {
|
||||
args->get_IsSuccess(&navigationSucceeded);
|
||||
}
|
||||
notifyWebView2WarmupReady(navigationSucceeded == TRUE);
|
||||
return S_OK;
|
||||
}
|
||||
).Get(),
|
||||
&navigationToken
|
||||
);
|
||||
|
||||
std::wstring url = toWide(controlsUrl);
|
||||
HRESULT navigateResult = webView->Navigate(url.c_str());
|
||||
if (FAILED(navigateResult)) {
|
||||
notifyWebView2WarmupReady(false);
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
).Get()
|
||||
);
|
||||
if (FAILED(controllerCallResult)) {
|
||||
notifyWebView2WarmupReady(false);
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
).Get()
|
||||
);
|
||||
if (FAILED(envCallResult)) {
|
||||
notifyWebView2WarmupReady(false);
|
||||
} else {
|
||||
MSG msg = {};
|
||||
while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (webView && messageToken.value != 0) {
|
||||
webView->remove_WebMessageReceived(messageToken);
|
||||
}
|
||||
if (webView && navigationToken.value != 0) {
|
||||
webView->remove_NavigationCompleted(navigationToken);
|
||||
}
|
||||
if (controller) {
|
||||
controller->Close();
|
||||
controller.Reset();
|
||||
}
|
||||
webView.Reset();
|
||||
environment.Reset();
|
||||
if (didOleInitialize) {
|
||||
OleUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
bool startWebView2Warmup(const std::string &controlsUrl) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gWebView2WarmupMutex);
|
||||
if (!gWebView2WarmupStarted) {
|
||||
gWebView2WarmupStarted = true;
|
||||
gWebView2WarmupReady = false;
|
||||
gWebView2WarmupSucceeded = false;
|
||||
gWebView2WarmupThread = std::thread(runWebView2WarmupThread, controlsUrl);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> waitLock(gWebView2WarmupMutex);
|
||||
bool completed = gWebView2WarmupCv.wait_for(
|
||||
waitLock,
|
||||
std::chrono::seconds(5),
|
||||
[]() { return gWebView2WarmupReady; }
|
||||
);
|
||||
if (!completed) return false;
|
||||
return gWebView2WarmupSucceeded;
|
||||
}
|
||||
|
||||
void stopWebView2Warmup() {
|
||||
std::thread threadToJoin;
|
||||
DWORD threadId = 0;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(gWebView2WarmupMutex);
|
||||
if (!gWebView2WarmupStarted) return;
|
||||
gWebView2WarmupCv.wait_for(
|
||||
lock,
|
||||
std::chrono::seconds(1),
|
||||
[]() { return gWebView2WarmupThreadId != 0; }
|
||||
);
|
||||
threadId = gWebView2WarmupThreadId;
|
||||
}
|
||||
|
||||
if (threadId != 0) {
|
||||
PostThreadMessageW(threadId, WM_QUIT, 0, 0);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gWebView2WarmupMutex);
|
||||
if (gWebView2WarmupThread.joinable()) {
|
||||
threadToJoin = std::move(gWebView2WarmupThread);
|
||||
}
|
||||
}
|
||||
if (threadToJoin.joinable()) {
|
||||
threadToJoin.join();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gWebView2WarmupMutex);
|
||||
gWebView2WarmupStarted = false;
|
||||
gWebView2WarmupReady = false;
|
||||
gWebView2WarmupSucceeded = false;
|
||||
gWebView2WarmupThreadId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class WindowsMpvWebPlayer : public std::enable_shared_from_this<WindowsMpvWebPlayer> {
|
||||
struct InitializationState {
|
||||
std::mutex mutex;
|
||||
|
|
@ -384,7 +596,7 @@ public:
|
|||
const std::vector<std::string> &headerLines,
|
||||
bool playWhenReady,
|
||||
long long initialPositionMs,
|
||||
const std::string &controlsHtml,
|
||||
const std::string &controlsUrl,
|
||||
JavaVM *vm,
|
||||
jobject sink,
|
||||
jmethodID method
|
||||
|
|
@ -401,8 +613,8 @@ public:
|
|||
auto initState = std::make_shared<InitializationState>();
|
||||
auto self = shared_from_this();
|
||||
uiThread = std::thread(
|
||||
[self, sourceUrl, headerLines, playWhenReady, initialPositionMs, controlsHtml, initState]() {
|
||||
self->runNativeUiThread(sourceUrl, headerLines, playWhenReady, initialPositionMs, controlsHtml, initState);
|
||||
[self, sourceUrl, headerLines, playWhenReady, initialPositionMs, controlsUrl, initState]() {
|
||||
self->runNativeUiThread(sourceUrl, headerLines, playWhenReady, initialPositionMs, controlsUrl, initState);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -693,12 +905,12 @@ private:
|
|||
std::vector<std::string> headerLines,
|
||||
bool playWhenReady,
|
||||
long long initialPositionMs,
|
||||
std::string controlsHtml,
|
||||
std::string controlsUrl,
|
||||
std::shared_ptr<InitializationState> initState
|
||||
) {
|
||||
std::string failure;
|
||||
try {
|
||||
initializeOnNativeUiThread(sourceUrl, headerLines, playWhenReady, initialPositionMs, controlsHtml);
|
||||
initializeOnNativeUiThread(sourceUrl, headerLines, playWhenReady, initialPositionMs, controlsUrl);
|
||||
} catch (const std::exception &error) {
|
||||
failure = error.what();
|
||||
cleanupUiResources();
|
||||
|
|
@ -727,7 +939,7 @@ private:
|
|||
const std::vector<std::string> &headerLines,
|
||||
bool playWhenReady,
|
||||
long long initialPositionMs,
|
||||
const std::string &controlsHtml
|
||||
const std::string &controlsUrl
|
||||
) {
|
||||
registerWindowClasses();
|
||||
uiThreadId = GetCurrentThreadId();
|
||||
|
|
@ -777,7 +989,7 @@ private:
|
|||
throw std::runtime_error("Unable to create native player container window.");
|
||||
}
|
||||
|
||||
startWebView(controlsHtml);
|
||||
startWebView(controlsUrl);
|
||||
startMpv(sourceUrl, headerLines, playWhenReady, initialPositionMs);
|
||||
layoutNativeSubviews();
|
||||
if (!SetTimer(messageHwnd, NUVIO_TIMER_ID, 500, nullptr)) {
|
||||
|
|
@ -852,7 +1064,7 @@ private:
|
|||
doneCv->wait(waitLock, [&]() { return *done; });
|
||||
}
|
||||
|
||||
void startWebView(const std::string &controlsHtml) {
|
||||
void startWebView(const std::string &controlsUrl) {
|
||||
std::wstring userDataDir = tempUserDataDirectory();
|
||||
auto weakSelf = weak_from_this();
|
||||
HRESULT result = CreateCoreWebView2EnvironmentWithOptions(
|
||||
|
|
@ -860,7 +1072,7 @@ private:
|
|||
userDataDir.c_str(),
|
||||
nullptr,
|
||||
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
|
||||
[weakSelf, controlsHtml](HRESULT envResult, ICoreWebView2Environment *createdEnvironment) -> HRESULT {
|
||||
[weakSelf, controlsUrl](HRESULT envResult, ICoreWebView2Environment *createdEnvironment) -> HRESULT {
|
||||
auto self = weakSelf.lock();
|
||||
if (!self || self->shuttingDown.load()) return S_OK;
|
||||
if (FAILED(envResult) || !createdEnvironment) {
|
||||
|
|
@ -871,7 +1083,7 @@ private:
|
|||
HRESULT controllerResult = createdEnvironment->CreateCoreWebView2Controller(
|
||||
self->containerHwnd,
|
||||
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
|
||||
[controllerWeakSelf, controlsHtml](HRESULT controllerResult, ICoreWebView2Controller *createdController) -> HRESULT {
|
||||
[controllerWeakSelf, controlsUrl](HRESULT controllerResult, ICoreWebView2Controller *createdController) -> HRESULT {
|
||||
auto controllerSelf = controllerWeakSelf.lock();
|
||||
if (!controllerSelf || controllerSelf->shuttingDown.load()) return S_OK;
|
||||
if (FAILED(controllerResult) || !createdController) {
|
||||
|
|
@ -910,8 +1122,8 @@ private:
|
|||
&controllerSelf->messageToken
|
||||
);
|
||||
controllerSelf->layoutNativeSubviews();
|
||||
std::wstring html = toWide(controlsHtml);
|
||||
controllerSelf->webView->NavigateToString(html.c_str());
|
||||
std::wstring url = toWide(controlsUrl);
|
||||
controllerSelf->webView->Navigate(url.c_str());
|
||||
createdController->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
|
||||
}
|
||||
return S_OK;
|
||||
|
|
@ -1512,13 +1724,13 @@ Java_com_nuvio_app_features_player_desktop_NativePlayerBridge_create(
|
|||
jobjectArray headerLines,
|
||||
jboolean playWhenReady,
|
||||
jlong initialPositionMs,
|
||||
jstring controlsHtml,
|
||||
jstring controlsPageUrl,
|
||||
jobject eventSink
|
||||
) {
|
||||
HWND hostHwnd = (HWND)(intptr_t)hostViewPtr;
|
||||
std::string sourceUrlText = jstringToUtf8(env, sourceUrl);
|
||||
std::vector<std::string> headerLineValues = jstringArrayToVector(env, headerLines);
|
||||
std::string controlsHtmlText = jstringToUtf8(env, controlsHtml);
|
||||
std::string controlsPageUrlText = jstringToUtf8(env, controlsPageUrl);
|
||||
JavaVM *javaVm = nullptr;
|
||||
env->GetJavaVM(&javaVm);
|
||||
|
||||
|
|
@ -1544,7 +1756,7 @@ Java_com_nuvio_app_features_player_desktop_NativePlayerBridge_create(
|
|||
headerLineValues,
|
||||
playWhenReady == JNI_TRUE,
|
||||
initialPositionMs,
|
||||
controlsHtmlText,
|
||||
controlsPageUrlText,
|
||||
javaVm,
|
||||
eventSinkRef,
|
||||
eventMethod
|
||||
|
|
@ -1560,6 +1772,17 @@ Java_com_nuvio_app_features_player_desktop_NativePlayerBridge_create(
|
|||
return handle;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_nuvio_app_features_player_desktop_NativePlayerBridge_warmupWebView2(JNIEnv *env, jobject, jstring controlsPageUrl) {
|
||||
std::string controlsPageUrlText = jstringToUtf8(env, controlsPageUrl);
|
||||
return startWebView2Warmup(controlsPageUrlText) ? JNI_TRUE : JNI_FALSE;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_nuvio_app_features_player_desktop_NativePlayerBridge_shutdownWebView2Warmup(JNIEnv *, jobject) {
|
||||
stopWebView2Warmup();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_nuvio_app_features_player_desktop_NativePlayerBridge_dispose(JNIEnv *, jobject, jlong handle) {
|
||||
if (handle == 0) return;
|
||||
|
|
|
|||
Loading…
Reference in a new issue