15 Commits

Author SHA1 Message Date
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
bd321520c9 chore: release v1.0.7 2026-09-16 18:47:31 -07:00
c869101923 chore: release v1.0.6 2026-09-09 14:48:05 -07:00
feccab9aa1 update 2026-09-09 14:39:54 -07:00
82c91334f6 fixed security issues, ty zstrike 2026-09-09 14:36:37 -07:00
a0de7b4bca chore: release v1.0.3 2026-09-08 18:07:48 -07:00
119b0ad28f removed the weird player logo on streams 2026-09-08 18:06:10 -07:00
6d38d9c335 v1.0.2 2026-09-08 16:58:04 -07:00
16 changed files with 6111 additions and 46 deletions

View File

@@ -1,3 +1,6 @@
## Development & Build
Install dependencies: Install dependencies:
```bash ```bash
npm install npm install
@@ -13,11 +16,18 @@ Compile and assemble the release APK:
npm run build:apk npm run build:apk
``` ```
Release to GitLab: ## Release
```bash
Set credentials once per session, then run release:
```powershell
$env:ANDROID_HOME = "$env:LOCALAPPDATA\Android\Sdk"
$env:NDK_HOME = "$env:LOCALAPPDATA\Android\Sdk\ndk\29.0.13846066"
$env:GITLAB_TOKEN = "your-token-here"
npm run release npm run release
``` ```
This builds the payload, compiles the APK for arm64 and x86_64, commits and tags the version, uploads all artifacts, and creates the GitLab release automatically.
## Logs ## Logs
Stream live device logs: Stream live device logs:
@@ -35,7 +45,7 @@ Dump logs to file:
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -d -s VylaGo > vyla-go.log & "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -d -s VylaGo > vyla-go.log
``` ```
Show all crash/fatal logs: Show all crash and fatal logs:
```powershell ```powershell
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -d | Select-String "VylaGo|AndroidRuntime|FATAL" & "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" logcat -d | Select-String "VylaGo|AndroidRuntime|FATAL"
``` ```

View File

@@ -3,31 +3,17 @@
Vyla Go is the native Android mobile launcher for the Vyla media ecosystem. Vyla Go is the native Android mobile launcher for the Vyla media ecosystem.
## Features ## Features
- **Native Android APK Architecture**: Built on Tauri v2 with native hardware acceleration. - **Native Android APK Architecture**: Built on Tauri v2 with native hardware acceleration.
- **Automated Resource Management**: Downloads, unpacks, and updates core services directly from official releases. - **Automated Resource Management**: Downloads, unpacks, and updates core services directly from official releases.
- **Hardware Shader Interface**: Real-time WebGL shader lighting and glassmorphism elements. - **Hardware Shader Interface**: Real-time WebGL shader lighting and glassmorphism elements.
## Prerequisites ## Prerequisites
- Node.js 20+ - Node.js 20+
- Rust & Cargo - Rust & Cargo
- Android Studio with Android SDK & NDK 29+ - Android Studio with Android SDK & NDK 29+
- Toolchain targets: - Toolchain targets:
```bash
rustup target add aarch64-linux-android
```
## Development & Build
Install dependencies:
```bash ```bash
npm install rustup target add aarch64-linux-android x86_64-linux-android
```
Compile and assemble the release APK:
```bash
npm run build:apk
```
Release packages to GitLab:
```bash
npm run release
``` ```

BIN
assets/logos/white_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -20,33 +20,91 @@ function cleanBuildDir() {
fs.mkdirSync(BUILD_DIR, { recursive: true }); fs.mkdirSync(BUILD_DIR, { recursive: true });
} }
function removeSensitiveFiles(dir) {
if (!fs.existsSync(dir)) return;
const gitDir = path.join(dir, '.git');
if (fs.existsSync(gitDir)) {
fs.rmSync(gitDir, { recursive: true, force: true });
}
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (entry.name === '.git') {
fs.rmSync(fullPath, { recursive: true, force: true });
} else {
removeSensitiveFiles(fullPath);
}
} else {
if (
entry.name.startsWith('.env') ||
entry.name.endsWith('.key') ||
entry.name.endsWith('.pem') ||
entry.name === 'credentials.json' ||
entry.name === '.gitlab-ci.yml'
) {
fs.rmSync(fullPath, { force: true });
}
}
}
}
function clonePrivateRepo(repoConfig, targetDir) { function clonePrivateRepo(repoConfig, targetDir) {
if (fs.existsSync(targetDir)) { if (fs.existsSync(targetDir)) {
fs.rmSync(targetDir, { recursive: true, force: true }); fs.rmSync(targetDir, { recursive: true, force: true });
} }
const token = process.env.GITLAB_TOKEN || process.env.VYLA_GITLAB_TOKEN; const token = process.env.GITLAB_TOKEN || process.env.VYLA_GITLAB_TOKEN;
const auth = token ? `oauth2:${token}@` : ''; const repoUrl = `https://gitlab.com/${GITLAB_GROUP}/${repoConfig.name}.git`;
const repoUrl = `https://${auth}gitlab.com/${GITLAB_GROUP}/${repoConfig.name}.git`;
execSync(`git clone --depth 1 --branch ${repoConfig.branch} ${repoUrl} ${targetDir}`, { stdio: 'inherit' }); try {
if (token) {
const authHeader = Buffer.from(`oauth2:${token.trim()}`).toString('base64');
execSync(
`git -c "http.extraHeader=Authorization: Basic ${authHeader}" clone --depth 1 --branch ${repoConfig.branch} ${repoUrl} "${targetDir}"`,
{ stdio: 'inherit' }
);
} else {
execSync(
`git clone --depth 1 --branch ${repoConfig.branch} ${repoUrl} "${targetDir}"`,
{ stdio: 'inherit' }
);
}
} finally {
const gitFolder = path.join(targetDir, '.git');
if (fs.existsSync(gitFolder)) {
fs.rmSync(gitFolder, { recursive: true, force: true });
}
}
} }
async function build() { async function build() {
cleanBuildDir(); cleanBuildDir();
for (const [key, config] of Object.entries(REPO_CONFIG)) { for (const [key, config] of Object.entries(REPO_CONFIG)) {
const targetDir = path.join(__dirname, key); const targetDir = path.join(__dirname, key);
console.log(`Cloning ${config.name}...`);
clonePrivateRepo(config, targetDir); clonePrivateRepo(config, targetDir);
console.log(`Installing production dependencies for ${config.name}...`);
execSync('npm install --omit=dev', { cwd: targetDir, stdio: 'inherit' }); execSync('npm install --omit=dev', { cwd: targetDir, stdio: 'inherit' });
removeSensitiveFiles(targetDir);
} }
const zip = new AdmZip(); const zip = new AdmZip();
const configDir = path.join(__dirname, 'config'); const configDir = path.join(__dirname, 'config');
if (fs.existsSync(configDir)) { if (fs.existsSync(configDir)) {
removeSensitiveFiles(configDir);
zip.addLocalFolder(configDir, 'config'); zip.addLocalFolder(configDir, 'config');
} }
for (const repo of REPOS) { for (const repo of REPOS) {
const srcRepo = path.join(__dirname, repo); const srcRepo = path.join(__dirname, repo);
removeSensitiveFiles(srcRepo);
zip.addLocalFolder(srcRepo, repo); zip.addLocalFolder(srcRepo, repo);
} }
@@ -54,6 +112,20 @@ async function build() {
if (fs.existsSync(zipPath)) fs.rmSync(zipPath, { force: true }); if (fs.existsSync(zipPath)) fs.rmSync(zipPath, { force: true });
zip.writeZip(zipPath); zip.writeZip(zipPath);
const verificationZip = new AdmZip(zipPath);
const leakedEntries = verificationZip.getEntries().filter(e =>
e.entryName.includes('.git/') ||
e.entryName.includes('.git\\') ||
e.entryName.startsWith('.env') ||
e.entryName.includes('/.env') ||
e.entryName.endsWith('config/encrypted-credentials.json')
);
if (leakedEntries.length > 0) {
fs.rmSync(zipPath, { force: true });
throw new Error(`SECURITY ABORT: Archive contains forbidden files:\n${leakedEntries.map(e => e.entryName).join('\n')}`);
}
const hash = crypto.createHash('sha256'); const hash = crypto.createHash('sha256');
hash.update(fs.readFileSync(zipPath)); hash.update(fs.readFileSync(zipPath));
const checksum = hash.digest('hex'); const checksum = hash.digest('hex');
@@ -68,6 +140,8 @@ async function build() {
for (const repo of REPOS) { for (const repo of REPOS) {
fs.rmSync(path.join(__dirname, repo), { recursive: true, force: true }); fs.rmSync(path.join(__dirname, repo), { recursive: true, force: true });
} }
console.log('Build complete and verified clean.');
} }
build().catch(err => { build().catch(err => {

View File

@@ -1,6 +1,6 @@
{ {
"name": "vyla-go", "name": "vyla-go",
"version": "1.0.2", "version": "1.0.11",
"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"
}, },

View File

@@ -32,11 +32,21 @@ function incrementVersion() {
function gitCommitAndTag(version) { function gitCommitAndTag(version) {
try { try {
execSync('git add package.json src-tauri/tauri.conf.json', { stdio: 'inherit' }); execSync('git add package.json src-tauri/tauri.conf.json', { stdio: 'inherit' });
execSync(`git commit -m "chore: release ${version}"`, { stdio: 'inherit' }); try {
execSync(`git tag -a ${version} -m "Release ${version}"`, { stdio: 'inherit' }); execSync(`git commit -m "chore: release ${version}"`, { stdio: 'inherit' });
} catch (_) {
console.log('Working tree clean, skipping commit.');
}
try {
execSync(`git tag -a ${version} -m "Release ${version}"`, { stdio: 'inherit' });
} catch (_) {
console.log(`Tag ${version} already exists locally.`);
}
execSync('git push origin HEAD --tags', { stdio: 'inherit' }); execSync('git push origin HEAD --tags', { stdio: 'inherit' });
} catch (e) { } catch (e) {
console.warn('Git operations failed (non-fatal):', e.message); console.warn('Git push failed (will fallback to GitLab API ref):', e.message);
} }
} }
@@ -94,6 +104,11 @@ async function createRelease(version, assets) {
const payload = assets.payload; const payload = assets.payload;
const manifest = assets.manifest; const manifest = assets.manifest;
let branch = 'main';
try {
branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim() || 'main';
} catch (_) { }
const description = [ const description = [
`## Automated Release ${version}`, `## Automated Release ${version}`,
'', '',
@@ -110,6 +125,7 @@ async function createRelease(version, assets) {
const body = JSON.stringify({ const body = JSON.stringify({
name: `Vyla Go Release ${version}`, name: `Vyla Go Release ${version}`,
tag_name: tag, tag_name: tag,
ref: branch,
description, description,
assets: { assets: {
links: [ links: [
@@ -137,11 +153,22 @@ async function main() {
const version = `v${rawVersion}`; const version = `v${rawVersion}`;
console.log(`Releasing ${version}...`); console.log(`Releasing ${version}...`);
const androidHome = process.env.ANDROID_HOME || `${process.env.LOCALAPPDATA}\\Android\\Sdk`;
const ndkHome = process.env.NDK_HOME || `${androidHome}\\ndk\\29.0.13846066`;
const buildEnv = {
...process.env,
ANDROID_HOME: androidHome,
NDK_HOME: ndkHome,
GITLAB_TOKEN: TOKEN,
VYLA_GITLAB_TOKEN: TOKEN,
};
console.log('Building payload...'); console.log('Building payload...');
execSync('npm run build:payload', { stdio: 'inherit' }); execSync('npm run build:payload', { stdio: 'inherit', env: buildEnv });
console.log('Building APK...'); console.log('Building APK...');
execSync('npm run build:apk', { stdio: 'inherit' }); execSync('npm run build:apk', { stdio: 'inherit', env: buildEnv });
const apkSearchDirs = [ const apkSearchDirs = [
path.join(__dirname, 'src-tauri', 'gen', 'android', 'app', 'build', 'outputs', 'apk', 'universal', 'release'), path.join(__dirname, 'src-tauri', 'gen', 'android', 'app', 'build', 'outputs', 'apk', 'universal', 'release'),

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>

View File

@@ -1,9 +1,36 @@
package com.vyla.go package com.vyla.go
import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.webkit.WebChromeClient
import android.webkit.WebView
class MainActivity : TauriActivity() { class MainActivity : TauriActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val root = findViewById<ViewGroup>(android.R.id.content)
root.viewTreeObserver.addOnGlobalLayoutListener {
findAndHookWebView(root)
}
}
private fun findAndHookWebView(view: View) {
if (view is WebView) {
view.webChromeClient = object : WebChromeClient() {
override fun getDefaultVideoPoster(): Bitmap {
return Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
}
}
return
}
if (view is ViewGroup) {
for (i in 0 until view.childCount) {
findAndHookWebView(view.getChildAt(i))
}
}
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -21,7 +21,6 @@ open class BuildTask : DefaultTask() {
runTauriCli(executable) runTauriCli(executable)
} catch (e: Exception) { } catch (e: Exception) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) { if (Os.isFamily(Os.FAMILY_WINDOWS)) {
// Try different Windows-specific extensions
val fallbacks = listOf( val fallbacks = listOf(
"$executable.exe", "$executable.exe",
"$executable.cmd", "$executable.cmd",

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,113 @@ 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));
"#;
fn android_log(msg: &str) { fn android_log(msg: &str) {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
{ {
@@ -135,6 +242,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");
@@ -150,26 +264,33 @@ fn spawn_service_manager(base_dir: &std::path::Path) {
let server_js = server_script.to_string_lossy().to_string(); let server_js = server_script.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"))]
@@ -323,6 +444,45 @@ async fn download_and_extract_payload(app: tauri::AppHandle, window: WebviewWind
Ok(base_dir) Ok(base_dir)
} }
const ANDROID_VIDEO_FIX_SCRIPT: &str = r#"
(function() {
function suppressNativeOverlay() {
const styleId = '__vyla_native_video_fix';
if (!document.getElementById(styleId)) {
const s = document.createElement('style');
s.id = styleId;
s.textContent = `
video::-webkit-media-controls,
video::-webkit-media-controls-enclosure,
video::-webkit-media-controls-panel,
video::-webkit-media-controls-overlay-play-button,
video::-webkit-media-controls-start-playback-button {
display: none !important;
-webkit-appearance: none !important;
opacity: 0 !important;
pointer-events: none !important;
}
`;
(document.head || document.documentElement).appendChild(s);
}
document.querySelectorAll('video').forEach(v => {
v.setAttribute('playsinline', '');
v.setAttribute('webkit-playsinline', '');
v.removeAttribute('poster');
});
}
suppressNativeOverlay();
new MutationObserver(suppressNativeOverlay).observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['poster']
});
})();
"#;
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
@@ -389,6 +549,10 @@ pub fn run() {
.setup(|app| { .setup(|app| {
let handle = app.handle().clone(); let handle = app.handle().clone();
if let Some(window) = handle.get_webview_window("main") {
let _ = window.eval(ANDROID_VIDEO_FIX_SCRIPT);
}
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(400));
@@ -398,7 +562,10 @@ pub fn run() {
match download_and_extract_payload(handle.clone(), window.clone()).await { match download_and_extract_payload(handle.clone(), window.clone()).await {
Ok(_) => { Ok(_) => {
std::thread::sleep(std::time::Duration::from_millis(400)); std::thread::sleep(std::time::Duration::from_millis(400));
let _ = window.eval("window.location.replace('http://vyla.localhost/frontend/index.html');"); let _ = window.eval(&format!(
"{}; window.location.replace('http://vyla.localhost/frontend/index.html');",
ANDROID_VIDEO_FIX_SCRIPT
));
} }
Err(err) => { Err(err) => {
update_ui( update_ui(

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.2", "version": "1.0.11",
"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">