14 Commits

Author SHA1 Message Date
60b24bf9e4 chore: release v1.0.14 2026-10-28 20:46:40 -07:00
3958f751ee feat: launch player and frontend servers in a single Node process 2026-10-28 20:45:10 -07:00
72b752fc02 chore: release v1.0.13 2026-10-28 20:33:25 -07:00
c7463364da fix: improve Android release check networking and restore loading screen logo 2026-10-28 20:31:57 -07:00
084d215b23 chore: release v1.0.12 2026-10-28 20:21:02 -07:00
a4b3a77153 fix: robust payload download, multi-endpoint check and auto-recovery 2026-10-28 20:19:40 -07:00
Devin Bot
9b9d0dfc49 chore: release v1.0.11 2026-10-25 20:05:35 -07:00
Devin Bot
7f7a969698 chore: release v1.0.10 2026-10-25 11:36:59 -07:00
Devin Bot
fd738eeba5 fixed crashing issues 2026-10-23 17:08:29 -07:00
Devin Bot
a28239cf60 chore: release v1.0.9 2026-10-23 17:07:28 -07:00
Devin Bot
966115c456 added logo img 2026-10-23 15:38:27 -07:00
2c0cc5c933 Merge branch 'main' of https://gitlab.com/vyla-entertainment/go 2026-10-03 12:16:10 -07:00
0e385aae9e chore: release v1.0.8 2026-10-03 12:15:18 -07:00
cd18f09142 Android TV build support 2026-09-26 09:41:32 -04:00
13 changed files with 6107 additions and 54 deletions

View File

@@ -64,4 +64,9 @@ Force-stop and clear app data:
```powershell ```powershell
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell am force-stop com.vyla.go & "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell am force-stop com.vyla.go
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell pm clear com.vyla.go & "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell pm clear com.vyla.go
``` ```
Fix emulator DNS timeouts (if emulator gets network timeouts connecting to GitLab):
```powershell
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell settings put global private_dns_mode off
```

BIN
assets/logos/white_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "vyla-go", "name": "vyla-go",
"version": "1.0.7", "version": "1.0.14",
"description": "Vyla Go Mobile Launcher", "description": "Vyla Go Mobile Launcher",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
@@ -16,7 +16,7 @@
"@tauri-apps/plugin-opener": "^2.0.0", "@tauri-apps/plugin-opener": "^2.0.0",
"@tauri-apps/plugin-process": "^2.0.0", "@tauri-apps/plugin-process": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.0", "@tauri-apps/plugin-shell": "^2.0.0",
"@vyla-entertainment/sdk": "^1.11.0", "@vyla-entertainment/sdk": "^1.18.0",
"adm-zip": "^0.6.0", "adm-zip": "^0.6.0",
"express": "^4.19.2" "express": "^4.19.2"
}, },

1
src-tauri/Cargo.lock generated
View File

@@ -4593,6 +4593,7 @@ dependencies = [
"tauri-plugin-opener", "tauri-plugin-opener",
"tauri-plugin-process", "tauri-plugin-process",
"tauri-plugin-shell", "tauri-plugin-shell",
"tokio",
"urlencoding", "urlencoding",
"zip", "zip",
] ]

View File

@@ -23,3 +23,4 @@ reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"
zip = { version = "2.2", default-features = false, features = ["deflate"] } zip = { version = "2.2", default-features = false, features = ["deflate"] }
futures-util = "0.3" futures-util = "0.3"
urlencoding = "2.1" urlencoding = "2.1"
tokio = { version = "1", features = ["time"] }

View File

@@ -13,6 +13,14 @@ val tauriProperties = Properties().apply {
} }
} }
configurations.all {
resolutionStrategy {
force("com.fasterxml.jackson.core:jackson-databind:2.13.5")
force("com.fasterxml.jackson.core:jackson-core:2.13.5")
force("com.fasterxml.jackson.core:jackson-annotations:2.13.5")
}
}
android { android {
compileSdk = 36 compileSdk = 36
namespace = "com.vyla.go" namespace = "com.vyla.go"
@@ -23,6 +31,9 @@ android {
targetSdk = 36 targetSdk = 36
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
ndk {
abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a", "x86_64"))
}
} }
signingConfigs { signingConfigs {
create("release") { create("release") {
@@ -59,6 +70,11 @@ android {
useLegacyPackaging = true useLegacyPackaging = true
} }
} }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
isCoreLibraryDesugaringEnabled = true
}
} }
rust { rust {
@@ -74,6 +90,7 @@ dependencies {
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4") androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
} }
apply(from = "tauri.build.gradle.kts") apply(from = "tauri.build.gradle.kts")

View File

@@ -2,15 +2,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<application <application
android:label="@string/app_name" android:label="@string/app_name"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:banner="@drawable/tv_banner"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:largeHeap="true" android:largeHeap="true"
android:extractNativeLibs="true" android:extractNativeLibs="true"
android:supportsRtl="true"
android:theme="@style/Theme.vyla_go"> android:theme="@style/Theme.vyla_go">
<activity <activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
@@ -22,6 +26,10 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity> </activity>
</application> </application>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,135 @@ const RUNTIME_INTERCEPT_SCRIPT: &str = r#"
})(); })();
"#; "#;
const NODE_PRELOAD_SCRIPT: &str = r#"
const Module = require('module');
const origLoad = Module._load;
const stub = new Proxy(function () {}, {
get: (t, k) => (typeof k === 'symbol' || k === 'then' ? undefined : stub),
apply: () => stub,
construct: () => stub,
});
function buildTlsShim() {
class Session {
constructor(opts) {
this.opts = opts || {};
this.headers = this.opts.headers || {};
}
async request(method, url, options) {
options = options || {};
const headers = Object.assign({}, this.headers, options.headers || {});
const init = {
method,
headers,
redirect: options.followRedirects === false ? 'manual' : 'follow',
};
let body = options.body !== undefined ? options.body : options.data;
if (options.json !== undefined) {
body = JSON.stringify(options.json);
if (!headers['content-type'] && !headers['Content-Type']) {
headers['content-type'] = 'application/json';
}
} else if (body !== undefined && body !== null && typeof body === 'object' && !Buffer.isBuffer(body) && !(body instanceof URLSearchParams)) {
body = new URLSearchParams(body).toString();
if (!headers['content-type'] && !headers['Content-Type']) {
headers['content-type'] = 'application/x-www-form-urlencoded';
}
}
if (body !== undefined && body !== null && method !== 'GET' && method !== 'HEAD') {
init.body = body;
}
let ms = Number(options.timeout || this.opts.timeout || 30000);
if (ms < 1000) ms = ms * 1000;
init.signal = AbortSignal.timeout(ms);
const res = await fetch(url, init);
const text = await res.text();
const outHeaders = {};
res.headers.forEach((v, k) => { outHeaders[k] = v; });
return {
id: String(Date.now()),
status: res.status,
ok: res.ok,
success: res.ok,
url: res.url,
target: res.url,
headers: outHeaders,
cookies: {},
body: text,
text: async () => text,
json: async () => JSON.parse(text),
};
}
get(url, o) { return this.request('GET', url, o); }
post(url, o) { return this.request('POST', url, o); }
put(url, o) { return this.request('PUT', url, o); }
patch(url, o) { return this.request('PATCH', url, o); }
delete(url, o) { return this.request('DELETE', url, o); }
head(url, o) { return this.request('HEAD', url, o); }
options(url, o) { return this.request('OPTIONS', url, o); }
async close() {}
async free() {}
}
const ClientIdentifier = new Proxy({}, { get: (t, k) => (typeof k === 'symbol' ? undefined : String(k)) });
const mod = {
Session,
ClientIdentifier,
initTLS: async () => {},
destroyTLS: async () => {},
};
mod.default = mod;
return mod;
}
let tlsShim = null;
Module._load = function (request) {
if (request === 'node-tls-client') {
if (!tlsShim) {
tlsShim = buildTlsShim();
console.error('node-tls-client replaced with fetch shim');
}
return tlsShim;
}
try {
return origLoad.apply(this, arguments);
} catch (e) {
const msg = String(e && e.message);
if (/koffi/i.test(request) || /koffi/i.test(msg)) {
console.error('stubbed native module: ' + request + ' (' + msg + ')');
return stub;
}
throw e;
}
};
process.on('uncaughtException', (e) => console.error('uncaughtException', e && e.stack || e));
process.on('unhandledRejection', (e) => console.error('unhandledRejection', e && e.stack || e));
"#;
const LAUNCHER_SCRIPT: &str = r#"
const path = require('path');
const { pathToFileURL } = require('url');
async function boot(file, port) {
process.env.PORT = String(port);
try {
await import(pathToFileURL(file).href);
} catch (e) {
console.error('failed to start ' + file, e && e.stack || e);
} finally {
process.env.PORT = '3000';
}
}
(async () => {
const root = process.env.VYLA_ROOT;
await boot(path.join(root, 'player', 'server.js'), 3000);
await boot(path.join(root, 'frontend', 'server.js'), 3001);
})();
"#;
fn android_log(msg: &str) { fn android_log(msg: &str) {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
{ {
@@ -135,6 +264,13 @@ fn spawn_service_manager(base_dir: &std::path::Path) {
.map(|d| d.to_string_lossy().to_string()) .map(|d| d.to_string_lossy().to_string())
.unwrap_or_default(); .unwrap_or_default();
let tmp_dir = base_dir.join("tmp");
let _ = std::fs::create_dir_all(&tmp_dir);
std::env::set_var("HOME", base_dir.to_string_lossy().to_string());
std::env::set_var("TMPDIR", tmp_dir.to_string_lossy().to_string());
let preload_path = base_dir.join("preload.js");
let _ = std::fs::write(&preload_path, NODE_PRELOAD_SCRIPT);
let preload_str = preload_path.to_string_lossy().to_string();
std::env::set_var("PORT", "3000"); std::env::set_var("PORT", "3000");
std::env::set_var("NODE_ENV", "production"); std::env::set_var("NODE_ENV", "production");
std::env::set_var("TMDB_API_KEY", "3f6703f3e2a0b2b40fe4fbb3e1546244"); std::env::set_var("TMDB_API_KEY", "3f6703f3e2a0b2b40fe4fbb3e1546244");
@@ -147,29 +283,39 @@ fn spawn_service_manager(base_dir: &std::path::Path) {
); );
} }
let server_js = server_script.to_string_lossy().to_string(); let launcher_path = base_dir.join("launcher.js");
let _ = std::fs::write(&launcher_path, LAUNCHER_SCRIPT);
std::env::set_var("VYLA_ROOT", base_dir.to_string_lossy().to_string());
let server_js = launcher_path.to_string_lossy().to_string();
let player_dir_str = player_dir.to_string_lossy().to_string(); let player_dir_str = player_dir.to_string_lossy().to_string();
std::thread::spawn(move || { let _ = std::thread::Builder::new().stack_size(16 * 1024 * 1024).spawn(move || {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
{ {
let _ = std::env::set_current_dir(&player_dir_str); let _ = std::env::set_current_dir(&player_dir_str);
let arg0 = std::ffi::CString::new("node").unwrap(); let parts: [&str; 4] = ["node", "--require", preload_str.as_str(), server_js.as_str()];
let arg1 = std::ffi::CString::new(server_js.as_str()).unwrap(); let mut buf: Vec<u8> = Vec::new();
let mut offsets: Vec<usize> = Vec::new();
for p in parts.iter() {
offsets.push(buf.len());
buf.extend_from_slice(p.as_bytes());
buf.push(0);
}
buf.shrink_to_fit();
let mut argv: Vec<*mut std::os::raw::c_char> = vec![ let base = buf.as_mut_ptr() as *mut std::os::raw::c_char;
arg0.as_ptr() as *mut _, let mut argv: Vec<*mut std::os::raw::c_char> = offsets
arg1.as_ptr() as *mut _, .iter()
std::ptr::null_mut(), .map(|o| unsafe { base.add(*o) })
]; .collect();
argv.push(std::ptr::null_mut());
std::mem::forget(buf);
android_log("calling node::Start via FFI"); android_log("calling node::Start via FFI");
let ret = unsafe { node_ffi::_ZN4node5StartEiPPc(2, argv.as_mut_ptr()) }; let ret = unsafe { node_ffi::_ZN4node5StartEiPPc(4, argv.as_mut_ptr()) };
android_log(&format!("node::Start returned: {}", ret)); android_log(&format!("node::Start returned: {}", ret));
drop(arg0);
drop(arg1);
} }
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
@@ -179,6 +325,7 @@ fn spawn_service_manager(base_dir: &std::path::Path) {
}); });
let _ = wait_for_port(3000, Duration::from_secs(45)); let _ = wait_for_port(3000, Duration::from_secs(45));
let _ = wait_for_port(3001, Duration::from_secs(45));
} }
fn apply_directory_replacements(dir: &std::path::Path, find_val: &str, replace_val: &str) { fn apply_directory_replacements(dir: &std::path::Path, find_val: &str, replace_val: &str) {
@@ -231,44 +378,160 @@ async fn download_and_extract_payload(app: tauri::AppHandle, window: WebviewWind
let base_dir = get_app_dir(&app); let base_dir = get_app_dir(&app);
std::fs::create_dir_all(&base_dir).map_err(|e| format!("Storage dir error: {}", e))?; std::fs::create_dir_all(&base_dir).map_err(|e| format!("Storage dir error: {}", e))?;
let marker = base_dir.join(".installed");
let frontend_index = base_dir.join("frontend").join("index.html"); let frontend_index = base_dir.join("frontend").join("index.html");
if frontend_index.exists() { let server_script = base_dir.join("player").join("server.js");
let frontend_server = base_dir.join("frontend").join("server.js");
if marker.exists() && frontend_index.exists() && server_script.exists() && frontend_server.exists() {
android_log("App already installed, launching...");
apply_patches(&base_dir); apply_patches(&base_dir);
spawn_service_manager(&base_dir); spawn_service_manager(&base_dir);
update_ui(&window, "Launching Vyla Go...", Some(100)); update_ui(&window, "Launching Vyla Go...", Some(100));
return Ok(base_dir); return Ok(base_dir);
} }
update_ui(&window, "Connecting to GitLab...", Some(10)); let local_payload_candidates = [
base_dir.join("update-payload.zip"),
base_dir.join("temp-payload.zip"),
PathBuf::from("/sdcard/Download/update-payload.zip"),
PathBuf::from("/data/local/tmp/update-payload.zip"),
];
for candidate in &local_payload_candidates {
if candidate.exists() {
if let Ok(meta) = std::fs::metadata(candidate) {
if meta.len() > 10_000_000 {
if let Ok(f) = File::open(candidate) {
if let Ok(archive) = zip::ZipArchive::new(f) {
if archive.len() > 0 {
android_log(&format!("Found valid local payload: {}", candidate.display()));
update_ui(&window, "Unpacking local package...", Some(80));
let extract_dir = base_dir.clone();
let candidate_path = candidate.clone();
let extract_ok = tauri::async_runtime::spawn_blocking(move || -> Result<(), String> {
let zip_file = File::open(&candidate_path).map_err(|e| format!("Open zip error: {}", e))?;
let mut archive = zip::ZipArchive::new(zip_file).map_err(|e| format!("Zip parse error: {}", e))?;
archive.extract(&extract_dir).map_err(|e| format!("Extraction error: {}", e))?;
let _ = std::fs::write(extract_dir.join(".installed"), "ok");
apply_patches(&extract_dir);
Ok(())
})
.await
.map_err(|e| e.to_string())?;
if extract_ok.is_ok() {
spawn_service_manager(&base_dir);
update_ui(&window, "Launching Vyla Go...", Some(100));
return Ok(base_dir);
}
}
}
}
}
}
}
}
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
.user_agent("Mozilla/5.0 (Android) VylaGo/1.0") .user_agent("Mozilla/5.0 (Android) VylaGo/1.0")
.connect_timeout(std::time::Duration::from_secs(30)) .connect_timeout(std::time::Duration::from_secs(15))
.timeout(std::time::Duration::from_secs(180))
.redirect(reqwest::redirect::Policy::limited(10)) .redirect(reqwest::redirect::Policy::limited(10))
.local_address(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED))
.http1_only()
.pool_max_idle_per_host(0)
.build() .build()
.map_err(|e| format!("Client error: {}", e))?; .map_err(|e| format!("Client error: {}", e))?;
let releases_url = "https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/releases"; let release_endpoints = [
let res = client.get(releases_url).send().await.map_err(|e| format!("Release check failed: {}", e))?; "https://gitlab.com/api/v4/projects/vyla-entertainment%2Fgo/releases",
let json: serde_json::Value = res.json().await.map_err(|e| format!("JSON error: {}", e))?; "https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/releases",
];
let download_url = json.get(0) let mut download_url: Option<String> = None;
.and_then(|r| r.get("assets")) let mut last_error = String::new();
.and_then(|a| a.get("links"))
.and_then(|l| l.as_array()) const MAX_RETRIES: u32 = 4;
.and_then(|links| { for attempt in 1..=MAX_RETRIES {
links.iter().find(|item| { let msg = if attempt == 1 {
item.get("name").and_then(|n| n.as_str()) == Some("update-payload.zip") "Connecting to GitLab...".to_string()
}) } else {
}) format!("Connecting to GitLab (attempt {}/{})...", attempt, MAX_RETRIES)
.and_then(|asset| asset.get("url")) };
.and_then(|u| u.as_str()) update_ui(&window, &msg, Some(10 + (attempt - 1) * 3));
.ok_or_else(|| "Could not locate update-payload.zip link in release assets".to_string())?;
for endpoint in &release_endpoints {
android_log(&format!("Checking endpoint: {} (attempt {})", endpoint, attempt));
match client.get(*endpoint).send().await {
Ok(res) => {
if res.status().is_success() {
if let Ok(json) = res.json::<serde_json::Value>().await {
if let Some(releases) = json.as_array() {
for rel in releases {
if let Some(links) = rel.get("assets").and_then(|a| a.get("links")).and_then(|l| l.as_array()) {
for item in links {
if item.get("name").and_then(|n| n.as_str()) == Some("update-payload.zip") {
if let Some(url) = item.get("url").and_then(|u| u.as_str()) {
download_url = Some(url.to_string());
break;
}
}
}
}
if download_url.is_some() {
break;
}
}
}
}
} else {
last_error = format!("HTTP {}", res.status());
}
}
Err(e) => {
let mut detail = format!("{}", e);
let mut src = StdError::source(&e);
while let Some(s) = src {
detail.push_str(&format!(" <- {}", s));
src = s.source();
}
android_log(&format!("Release check failed for {}: {}", endpoint, detail));
last_error = detail;
}
}
if download_url.is_some() {
break;
}
}
if download_url.is_some() {
break;
}
if attempt < MAX_RETRIES {
let backoff = attempt * 2;
update_ui(
&window,
&format!("Connection pending, retrying in {}s...", backoff),
Some(10 + attempt * 3),
);
tokio::time::sleep(std::time::Duration::from_secs(backoff as u64)).await;
}
}
let download_url = download_url.ok_or_else(|| {
format!(
"Could not connect to release server ({})",
if last_error.is_empty() { "timeout" } else { &last_error }
)
})?;
update_ui(&window, "Downloading update payload...", Some(25)); update_ui(&window, "Downloading update payload...", Some(25));
android_log(&format!("Downloading from: {}", download_url)); android_log(&format!("Downloading from: {}", download_url));
let response = client.get(download_url).send().await.map_err(|e| format!("Download error: {} (source: {:?})", e, StdError::source(&e)))?; android_log(&format!("Response status: {}", response.status())); let response = client.get(&download_url).send().await.map_err(|e| format!("Download error: {} (source: {:?})", e, StdError::source(&e)))?;
android_log(&format!("Response status: {}", response.status()));
let total_size = response.content_length().unwrap_or(0) as usize; let total_size = response.content_length().unwrap_or(0) as usize;
let temp_zip_path = base_dir.join("temp-payload.zip"); let temp_zip_path = base_dir.join("temp-payload.zip");
@@ -287,7 +550,7 @@ async fn download_and_extract_payload(app: tauri::AppHandle, window: WebviewWind
downloaded += chunk.len(); downloaded += chunk.len();
let percent = if total_size > 0 { let percent = if total_size > 0 {
10 + ((downloaded as f64 / total_size as f64) * 75.0) as u32 25 + ((downloaded as f64 / total_size as f64) * 65.0) as u32
} else { } else {
35 35
}; };
@@ -310,7 +573,8 @@ async fn download_and_extract_payload(app: tauri::AppHandle, window: WebviewWind
let zip_file = File::open(&zip_path_clone).map_err(|e| format!("Open zip error: {}", e))?; let zip_file = File::open(&zip_path_clone).map_err(|e| format!("Open zip error: {}", e))?;
let mut archive = zip::ZipArchive::new(zip_file).map_err(|e| format!("Zip parse error: {}", e))?; let mut archive = zip::ZipArchive::new(zip_file).map_err(|e| format!("Zip parse error: {}", e))?;
archive.extract(&extract_dir).map_err(|e| format!("Extraction error: {}", e))?; archive.extract(&extract_dir).map_err(|e| format!("Extraction error: {}", e))?;
let _ = std::fs::remove_file(zip_path_clone); let _ = std::fs::remove_file(&zip_path_clone);
let _ = std::fs::write(extract_dir.join(".installed"), "ok");
apply_patches(&extract_dir); apply_patches(&extract_dir);
Ok(()) Ok(())
}) })
@@ -433,25 +697,32 @@ pub fn run() {
} }
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
std::thread::sleep(std::time::Duration::from_millis(400)); std::thread::sleep(std::time::Duration::from_millis(600));
if let Some(window) = handle.get_webview_window("main") { if let Some(window) = handle.get_webview_window("main") {
update_ui(&window, "Initializing Vyla Go engine...", Some(5)); update_ui(&window, "Initializing Vyla Go engine...", Some(5));
match download_and_extract_payload(handle.clone(), window.clone()).await { loop {
Ok(_) => { match download_and_extract_payload(handle.clone(), window.clone()).await {
std::thread::sleep(std::time::Duration::from_millis(400)); Ok(_) => {
let _ = window.eval(&format!( std::thread::sleep(std::time::Duration::from_millis(400));
"{}; window.location.replace('http://vyla.localhost/frontend/index.html');", let _ = window.eval(&format!(
ANDROID_VIDEO_FIX_SCRIPT "{}; window.location.replace('http://localhost:3001/');",
)); ANDROID_VIDEO_FIX_SCRIPT
} ));
Err(err) => { break;
update_ui( }
&window, Err(err) => {
&format!("Error: {}", err), android_log(&format!("Installer error: {}", err));
Some(0), for remaining in (1..=8).rev() {
); update_ui(
&window,
&format!("Error: {}. Retrying in {}s...", err, remaining),
Some(0),
);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
}
} }
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Vyla Go", "productName": "Vyla Go",
"version": "1.0.7", "version": "1.0.14",
"identifier": "com.vyla.go", "identifier": "com.vyla.go",
"build": { "build": {
"beforeDevCommand": "", "beforeDevCommand": "",

View File

@@ -122,7 +122,7 @@
</div> </div>
<div class="center-brand"> <div class="center-brand">
<img src="https://gitlab.com/vyla-entertainment/home-frontend/-/raw/main/assets/images/logo.png" alt="Logo" /> <img src="https://gitlab.com/vyla-entertainment/go/-/raw/main/assets/logos/white_logo.png" alt="Logo" />
</div> </div>
<div class="bottom-dock"> <div class="bottom-dock">