mirror of
https://github.com/This-null/flexcam.git
synced 2026-09-03 07:53:51 +08:00
Add live preview, WiFi access code, and OBS-free virtual camera
- Live camera preview on phone and PC - WiFi access code (PIN): USB/loopback exempt, blocks strangers on the network - Bundled Unity Capture (MIT) virtual camera driver - OBS now optional - Larger default window, sidebar overflow protection - Full install/build instructions in all 10 language READMEs
This commit is contained in:
60
README.md
60
README.md
@@ -25,37 +25,44 @@
|
||||
|
||||
---
|
||||
|
||||
> [!CAUTION]
|
||||
> **FlexCam requires [OBS Studio](https://obsproject.com/download) to be installed.**
|
||||
> It uses the OBS Virtual Camera driver to expose your phone as a webcam.
|
||||
> Install OBS once — you don't need to keep it open.
|
||||
|
||||
---
|
||||
|
||||
## What it is
|
||||
|
||||
FlexCam streams your Android phone's camera to your PC and exposes it as
|
||||
**"OBS Virtual Camera"**, so any app (Zoom, Teams, Discord, your browser, OBS)
|
||||
can pick it as a webcam. It works over **USB** (via ADB) and **WiFi**, and
|
||||
switches between them automatically if one drops.
|
||||
FlexCam streams your Android phone's camera to your PC and exposes it as a
|
||||
**virtual webcam**, so any app (Zoom, Teams, Discord, your browser, OBS) can
|
||||
pick it as a camera. It works over **USB** (via ADB) and **WiFi**, and switches
|
||||
between them automatically if one drops.
|
||||
|
||||
- Hybrid **USB + WiFi** with automatic failover
|
||||
- **Auto-connects** when your phone is ready — no manual steps
|
||||
- **Front / back** camera switch
|
||||
- **Auto portrait/landscape** (follows the phone's rotation)
|
||||
- **Live preview** on both phone and PC
|
||||
- **Front / back** camera switch, **auto portrait/landscape**
|
||||
- Keeps streaming with the **screen off**
|
||||
- **WiFi access code** — strangers on your network can't view the stream
|
||||
- **10 languages**, dark UI, tray support
|
||||
- Runs fully on your device — **no data leaves your computer**
|
||||
- Runs fully on your device — **no data ever leaves your computer**
|
||||
|
||||
## Virtual camera driver
|
||||
|
||||
FlexCam needs a virtual camera on the PC. You have two options:
|
||||
|
||||
- **Bundled driver (no OBS needed):** on first run, if no virtual camera is
|
||||
found, click **"Install virtual camera"** in the app. It registers a small
|
||||
open-source driver ([Unity Capture](https://github.com/schellingb/UnityCapture),
|
||||
MIT) once, with a one-time admin prompt.
|
||||
- **OBS Studio:** if you already have [OBS](https://obsproject.com/download),
|
||||
FlexCam uses its Virtual Camera automatically.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Install **OBS Studio** (once).
|
||||
2. On the phone: install `release/FlexCam.apk` and open the app.
|
||||
3. On the PC: run **FlexCam** and it auto-connects.
|
||||
4. In your video app, choose the camera **"OBS Virtual Camera"**.
|
||||
1. **Phone:** install `FlexCam.apk` and open the app.
|
||||
2. **PC:** unzip **FlexCam-Windows.zip** and run `FlexCam.exe`.
|
||||
- First run: if prompted, click **"Install virtual camera"** (one-time).
|
||||
3. It **auto-connects** over USB. For WiFi, type the phone's IP and the
|
||||
**access code** shown in the app.
|
||||
4. In your video app, pick the camera **"OBS Virtual Camera"** (or
|
||||
**"Unity Video Capture"** if you used the bundled driver).
|
||||
|
||||
USB needs **USB debugging** enabled (Developer Options). WiFi needs no cable —
|
||||
just type the IP the phone app shows.
|
||||
> USB needs **USB debugging** enabled (Developer Options). WiFi needs no cable.
|
||||
|
||||
## Build from source
|
||||
|
||||
@@ -74,10 +81,19 @@ python -m venv .venv
|
||||
.venv\Scripts\python webgui.py
|
||||
```
|
||||
|
||||
Package a standalone `.exe` with PyInstaller (onedir):
|
||||
```
|
||||
.venv\Scripts\python -m PyInstaller --noconfirm --onedir --windowed --name FlexCam ^
|
||||
--add-data "web;web" --add-data "flexcam-logo.png;." --add-data "FlexCam.apk;." ^
|
||||
--add-data "adb;adb" --add-data "virtualcam;virtualcam" ^
|
||||
--collect-all pyvirtualcam --collect-all pystray --collect-all webview ^
|
||||
--collect-all pythonnet --collect-all clr_loader --collect-all pypresence webgui.py
|
||||
```
|
||||
|
||||
## Tech
|
||||
|
||||
- **Android:** Kotlin, CameraX, MJPEG server on port `8474`.
|
||||
- **PC:** Python, pywebview UI, `pyvirtualcam` → OBS Virtual Camera.
|
||||
- **Android:** Kotlin, CameraX, an MJPEG server on port `8474`.
|
||||
- **PC:** Python, pywebview UI, `pyvirtualcam` → OBS or Unity Capture.
|
||||
- **Transport:** raw TCP over `adb forward` (USB) or the phone's IP (WiFi).
|
||||
|
||||
## License
|
||||
|
||||
@@ -38,6 +38,7 @@ class CameraCapture(
|
||||
fun pause() {
|
||||
paused = true
|
||||
latest.set(null)
|
||||
FrameBus.jpeg = null
|
||||
ContextCompat.getMainExecutor(context).execute { provider?.unbindAll() }
|
||||
}
|
||||
|
||||
@@ -59,7 +60,9 @@ class CameraCapture(
|
||||
.setTargetRotation(Surface.ROTATION_0)
|
||||
.build().also { a ->
|
||||
a.setAnalyzer(executor) { image ->
|
||||
latest.set(yuvToJpeg(image))
|
||||
val j = yuvToJpeg(image)
|
||||
latest.set(j)
|
||||
FrameBus.jpeg = j
|
||||
image.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@ import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import kotlin.concurrent.thread
|
||||
import androidx.core.content.ContextCompat.getColor
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@@ -58,11 +61,13 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
val ip = wifiIp()
|
||||
findViewById<TextView>(R.id.hintText).text = if (ip != null) {
|
||||
val base = if (ip != null) {
|
||||
getString(R.string.hint_with_ip, ip, Config.PORT)
|
||||
} else {
|
||||
getString(R.string.hint_no_ip, Config.PORT)
|
||||
}
|
||||
findViewById<TextView>(R.id.hintText).text =
|
||||
base + "\n" + getString(R.string.access_code, Pin.get(this))
|
||||
|
||||
findViewById<Button>(R.id.btnGithub).setOnClickListener { open(Config.GITHUB_URL) }
|
||||
findViewById<Button>(R.id.btnDiscord).setOnClickListener { open(Config.DISCORD_URL) }
|
||||
@@ -113,6 +118,34 @@ class MainActivity : AppCompatActivity() {
|
||||
if (hasFocus && !userStopped) maybeStartService()
|
||||
}
|
||||
|
||||
@Volatile private var previewing = false
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (previewing) return
|
||||
previewing = true
|
||||
val view = findViewById<ImageView>(R.id.preview)
|
||||
thread(name = "preview") {
|
||||
while (previewing) {
|
||||
val j = FrameBus.jpeg
|
||||
if (j != null) {
|
||||
val bmp = try {
|
||||
BitmapFactory.decodeByteArray(j, 0, j.size)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
if (bmp != null) runOnUiThread { view.setImageBitmap(bmp) }
|
||||
}
|
||||
Thread.sleep(66)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
previewing = false
|
||||
}
|
||||
|
||||
private fun requestPermissions() {
|
||||
val needed = mutableListOf(Manifest.permission.CAMERA)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
||||
@@ -9,6 +9,7 @@ class MjpegServer(
|
||||
private val port: Int,
|
||||
private val frameSource: () -> ByteArray?,
|
||||
private val control: (String) -> Unit = {},
|
||||
private val authKey: String = "",
|
||||
) {
|
||||
@Volatile private var running = false
|
||||
private var server: ServerSocket? = null
|
||||
@@ -46,7 +47,24 @@ class MjpegServer(
|
||||
try {
|
||||
client.use {
|
||||
val requestLine = it.getInputStream().bufferedReader().readLine() ?: ""
|
||||
val path = requestLine.split(" ").getOrNull(1) ?: "/"
|
||||
val target = requestLine.split(" ").getOrNull(1) ?: "/"
|
||||
val path = target.substringBefore("?")
|
||||
val query = target.substringAfter("?", "")
|
||||
|
||||
val loopback = it.inetAddress?.isLoopbackAddress ?: false
|
||||
if (!loopback && authKey.isNotEmpty()) {
|
||||
val key = query.split("&")
|
||||
.firstOrNull { q -> q.startsWith("key=") }
|
||||
?.substringAfter("key=") ?: ""
|
||||
if (key != authKey) {
|
||||
it.getOutputStream().write(
|
||||
"HTTP/1.0 403 Forbidden\r\nContent-Length: 9\r\n\r\nForbidden".toByteArray()
|
||||
)
|
||||
it.getOutputStream().flush()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (path.startsWith("/stop") || path.startsWith("/start")) {
|
||||
control(if (path.startsWith("/stop")) "stop" else "start")
|
||||
val ok = "HTTP/1.0 200 OK\r\nContent-Length: 2\r\n\r\nOK"
|
||||
|
||||
22
android/app/src/main/java/com/flexcam/Shared.kt
Normal file
22
android/app/src/main/java/com/flexcam/Shared.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.flexcam
|
||||
|
||||
import android.content.Context
|
||||
|
||||
object FrameBus {
|
||||
@Volatile
|
||||
var jpeg: ByteArray? = null
|
||||
}
|
||||
|
||||
object Pin {
|
||||
private const val CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||||
|
||||
fun get(ctx: Context): String {
|
||||
val p = ctx.getSharedPreferences("flexcam", Context.MODE_PRIVATE)
|
||||
var pin = p.getString("pin", null)
|
||||
if (pin == null) {
|
||||
pin = (1..4).map { CHARS.random() }.joinToString("")
|
||||
p.edit().putString("pin", pin).apply()
|
||||
}
|
||||
return pin
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ class WebcamService : LifecycleService() {
|
||||
Config.PORT,
|
||||
{ capture.latestJpeg() },
|
||||
{ cmd -> if (cmd == "stop") capture.pause() else capture.resume() },
|
||||
Pin.get(this),
|
||||
).also { it.start() }
|
||||
}
|
||||
|
||||
|
||||
@@ -36,10 +36,25 @@
|
||||
android:textColor="@color/muted"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="230dp"
|
||||
android:layout_marginTop="26dp"
|
||||
android:background="@drawable/bg_card">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:padding="6dp"
|
||||
android:scaleType="fitCenter" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:background="@drawable/bg_card"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="20dp"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<string name="status_perm_denied">Kamera izni reddedildi</string>
|
||||
<string name="hint_with_ip">PC\'de FlexCam:\nWiFi IP: %1$s\nPort: %2$d</string>
|
||||
<string name="hint_no_ip">PC\'de FlexCam\nPort: %1$d</string>
|
||||
<string name="access_code">WiFi erişim kodu: %1$s</string>
|
||||
<string name="notif_title">FlexCam çalışıyor</string>
|
||||
<string name="notif_text">Port %1$d — USB / WiFi yayında</string>
|
||||
</resources>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<string name="status_perm_denied">Camera permission denied</string>
|
||||
<string name="hint_with_ip">On PC (FlexCam):\nWiFi IP: %1$s\nPort: %2$d</string>
|
||||
<string name="hint_no_ip">On PC (FlexCam)\nPort: %1$d</string>
|
||||
<string name="access_code">WiFi access code: %1$s</string>
|
||||
<string name="notif_title">FlexCam is running</string>
|
||||
<string name="notif_text">Port %1$d — USB / WiFi live</string>
|
||||
</resources>
|
||||
|
||||
@@ -16,34 +16,70 @@
|
||||
|
||||
---
|
||||
|
||||
> [!CAUTION]
|
||||
> **FlexCam üçün [OBS Studio](https://obsproject.com/download) quraşdırılmalıdır.**
|
||||
> Telefonu veb-kamera kimi göstərmək üçün OBS Virtual Camera sürücüsündən istifadə edir.
|
||||
> OBS-i bir dəfə quraşdır — açıq saxlamağa ehtiyac yoxdur.
|
||||
|
||||
---
|
||||
|
||||
## Nədir
|
||||
|
||||
FlexCam Android telefonunun kamerasını PC-yə ötürür və onu **"OBS Virtual
|
||||
Camera"** kimi göstərir; beləliklə istənilən tətbiq (Zoom, Teams, Discord,
|
||||
brauzer, OBS) onu veb-kamera kimi seçə bilər. **USB** (ADB) və **WiFi** üzərindən
|
||||
işləyir və biri kəsilsə avtomatik digərinə keçir.
|
||||
FlexCam Android telefonunun kamerasını PC-yə ötürür və onu **virtual veb-kamera**
|
||||
kimi göstərir; beləliklə istənilən tətbiq (Zoom, Teams, Discord, brauzer, OBS)
|
||||
onu kamera kimi seçə bilər. **USB** (ADB) və **WiFi** üzərindən işləyir və biri
|
||||
kəsilsə avtomatik digərinə keçir.
|
||||
|
||||
- Hibrid **USB + WiFi**, avtomatik keçid
|
||||
- Telefon hazır olanda **avtomatik qoşulur**
|
||||
- **Ön / arxa** kamera dəyişimi
|
||||
- **Avtomatik dik/üfüqi**
|
||||
- Telefonda və PC-də **canlı önizləmə**
|
||||
- **Ön / arxa** kamera, **avtomatik dik/üfüqi**
|
||||
- **Ekran bağlı** ikən yayım davam edir
|
||||
- **10 dil**, qaranlıq interfeys
|
||||
- Tamamilə cihazında işləyir — **heç bir məlumat çıxmır**
|
||||
- **WiFi giriş kodu** — şəbəkədəki yad adamlar yayımı görə bilməz
|
||||
- **10 dil**, qaranlıq interfeys, trey dəstəyi
|
||||
- Tamamilə cihazında işləyir — **heç bir məlumat kompüterindən çıxmır**
|
||||
|
||||
## Virtual kamera sürücüsü
|
||||
|
||||
FlexCam PC-də virtual kameraya ehtiyac duyur. İki seçimin var:
|
||||
|
||||
- **Daxili sürücü (OBS lazım deyil):** İlk açılışda virtual kamera tapılmasa,
|
||||
tətbiqdə **"Virtual kamera quraşdır"** düyməsinə bas. Kiçik, açıq mənbəli bir
|
||||
sürücünü ([Unity Capture](https://github.com/schellingb/UnityCapture), MIT)
|
||||
bir dəfəlik admin icazəsi ilə qeydiyyatdan keçirir.
|
||||
- **OBS Studio:** Artıq [OBS](https://obsproject.com/download) quraşdırılıbsa,
|
||||
FlexCam onun virtual kamerasını avtomatik istifadə edir.
|
||||
|
||||
## Sürətli başlanğıc
|
||||
|
||||
1. **OBS Studio** quraşdır (bir dəfə).
|
||||
2. Telefonda: `release/FlexCam.apk` quraşdır və tətbiqi aç.
|
||||
3. PC-də: **FlexCam**-i işə sal — özü qoşulur.
|
||||
4. Video tətbiqində kamera olaraq **"OBS Virtual Camera"** seç.
|
||||
1. **Telefon:** `FlexCam.apk`-ı quraşdır və tətbiqi aç.
|
||||
2. **PC:** **FlexCam-Windows.zip**-i aç və `FlexCam.exe`-ni işə sal.
|
||||
- İlk açılış: soruşulsa **"Virtual kamera quraşdır"**a bas (bir dəfəlik).
|
||||
3. USB üzərindən **avtomatik qoşulur**. WiFi üçün telefonun IP-sini və tətbiqdə
|
||||
göstərilən **giriş kodunu** yaz.
|
||||
4. Video tətbiqində kamera olaraq **"OBS Virtual Camera"** (daxili sürücü ilə
|
||||
**"Unity Video Capture"**) seç.
|
||||
|
||||
> USB üçün **USB debugging** aktiv olmalıdır (Developer Options). WiFi kabel
|
||||
> tələb etmir.
|
||||
|
||||
## Mənbədən qurmaq
|
||||
|
||||
**Android (APK):**
|
||||
```
|
||||
cd android
|
||||
gradlew assembleDebug
|
||||
```
|
||||
> JDK 17 tələb olunur.
|
||||
|
||||
**PC (masaüstü tətbiqi):**
|
||||
```
|
||||
cd pc
|
||||
python -m venv .venv
|
||||
.venv\Scripts\python -m pip install -r requirements.txt
|
||||
.venv\Scripts\python webgui.py
|
||||
```
|
||||
|
||||
Müstəqil `.exe` PyInstaller (onedir) ilə — ingilis README-dəki əmrə bax.
|
||||
|
||||
## Texnologiya
|
||||
|
||||
- **Android:** Kotlin, CameraX, `8474` portunda MJPEG server.
|
||||
- **PC:** Python, pywebview interfeys, `pyvirtualcam` → OBS və ya Unity Capture.
|
||||
- **Ötürmə:** `adb forward` (USB) və ya telefonun IP-si (WiFi) üzərindən xam TCP.
|
||||
|
||||
## Lisenziya
|
||||
|
||||
|
||||
@@ -16,34 +16,71 @@
|
||||
|
||||
---
|
||||
|
||||
> [!CAUTION]
|
||||
> **FlexCam benötigt [OBS Studio](https://obsproject.com/download).**
|
||||
> Es nutzt die OBS Virtual Camera, um dein Handy als Webcam bereitzustellen.
|
||||
> Installiere OBS einmal — du musst es nicht geöffnet lassen.
|
||||
|
||||
---
|
||||
|
||||
## Was ist das
|
||||
|
||||
FlexCam überträgt die Kamera deines Android-Handys auf den PC und stellt sie als
|
||||
**„OBS Virtual Camera"** bereit, sodass jede App (Zoom, Teams, Discord, Browser,
|
||||
OBS) sie als Webcam auswählen kann. Es funktioniert über **USB** (ADB) und
|
||||
**virtuelle Webcam** bereit, sodass jede App (Zoom, Teams, Discord, Browser,
|
||||
OBS) sie als Kamera auswählen kann. Es funktioniert über **USB** (ADB) und
|
||||
**WLAN** und wechselt automatisch, falls eine Verbindung abbricht.
|
||||
|
||||
- Hybrid **USB + WLAN** mit automatischem Failover
|
||||
- **Verbindet sich automatisch**, sobald das Handy bereit ist
|
||||
- **Vordere / hintere** Kamera umschalten
|
||||
- **Automatisch Hoch-/Querformat**
|
||||
- **Live-Vorschau** auf Handy und PC
|
||||
- **Vordere / hintere** Kamera, **automatisch Hoch-/Querformat**
|
||||
- Streamt weiter bei **ausgeschaltetem Bildschirm**
|
||||
- **10 Sprachen**, dunkle Oberfläche
|
||||
- **WLAN-Zugangscode** — Fremde im Netzwerk können den Stream nicht sehen
|
||||
- **10 Sprachen**, dunkle Oberfläche, Infobereich
|
||||
- Läuft komplett auf deinem Gerät — **keine Daten verlassen deinen Computer**
|
||||
|
||||
## Treiber der virtuellen Kamera
|
||||
|
||||
FlexCam braucht eine virtuelle Kamera am PC. Du hast zwei Optionen:
|
||||
|
||||
- **Mitgelieferter Treiber (kein OBS nötig):** Wird beim ersten Start keine
|
||||
virtuelle Kamera gefunden, klicke in der App auf **„Virtuelle Kamera
|
||||
installieren"**. Registriert einmalig einen kleinen Open-Source-Treiber
|
||||
([Unity Capture](https://github.com/schellingb/UnityCapture), MIT) mit
|
||||
einmaliger Admin-Abfrage.
|
||||
- **OBS Studio:** Hast du bereits [OBS](https://obsproject.com/download), nutzt
|
||||
FlexCam dessen virtuelle Kamera automatisch.
|
||||
|
||||
## Schnellstart
|
||||
|
||||
1. **OBS Studio** installieren (einmalig).
|
||||
2. Am Handy: `release/FlexCam.apk` installieren und App öffnen.
|
||||
3. Am PC: **FlexCam** starten — es verbindet sich automatisch.
|
||||
4. In deiner Video-App die Kamera **„OBS Virtual Camera"** wählen.
|
||||
1. **Handy:** `FlexCam.apk` installieren und App öffnen.
|
||||
2. **PC:** **FlexCam-Windows.zip** entpacken und `FlexCam.exe` starten.
|
||||
- Erster Start: bei Aufforderung **„Virtuelle Kamera installieren"** (einmalig).
|
||||
3. Verbindet sich per USB **automatisch**. Für WLAN die Handy-IP und den in der
|
||||
App angezeigten **Zugangscode** eingeben.
|
||||
4. Wähle in deiner Video-App die Kamera **„OBS Virtual Camera"** (bzw.
|
||||
**„Unity Video Capture"** beim mitgelieferten Treiber).
|
||||
|
||||
> USB benötigt aktiviertes **USB-Debugging** (Entwickleroptionen). WLAN braucht
|
||||
> kein Kabel.
|
||||
|
||||
## Aus Quellcode bauen
|
||||
|
||||
**Android (APK):**
|
||||
```
|
||||
cd android
|
||||
gradlew assembleDebug
|
||||
```
|
||||
> Benötigt JDK 17.
|
||||
|
||||
**PC (Desktop-App):**
|
||||
```
|
||||
cd pc
|
||||
python -m venv .venv
|
||||
.venv\Scripts\python -m pip install -r requirements.txt
|
||||
.venv\Scripts\python webgui.py
|
||||
```
|
||||
|
||||
Standalone-`.exe` mit PyInstaller (onedir) — siehe den Befehl im englischen README.
|
||||
|
||||
## Technik
|
||||
|
||||
- **Android:** Kotlin, CameraX, MJPEG-Server auf Port `8474`.
|
||||
- **PC:** Python, pywebview-Oberfläche, `pyvirtualcam` → OBS oder Unity Capture.
|
||||
- **Übertragung:** rohes TCP über `adb forward` (USB) oder die Handy-IP (WLAN).
|
||||
|
||||
## Lizenz
|
||||
|
||||
|
||||
@@ -16,34 +16,71 @@
|
||||
|
||||
---
|
||||
|
||||
> [!CAUTION]
|
||||
> **FlexCam requiere [OBS Studio](https://obsproject.com/download).**
|
||||
> Usa la OBS Virtual Camera para exponer tu teléfono como webcam.
|
||||
> Instala OBS una vez — no necesitas mantenerlo abierto.
|
||||
|
||||
---
|
||||
|
||||
## Qué es
|
||||
|
||||
FlexCam transmite la cámara de tu teléfono Android al PC y la expone como
|
||||
**"OBS Virtual Camera"**, para que cualquier app (Zoom, Teams, Discord,
|
||||
navegador, OBS) pueda elegirla como webcam. Funciona por **USB** (ADB) y
|
||||
**WiFi**, y cambia automáticamente si una se cae.
|
||||
FlexCam transmite la cámara de tu teléfono Android al PC y la expone como una
|
||||
**webcam virtual**, para que cualquier app (Zoom, Teams, Discord, navegador,
|
||||
OBS) pueda elegirla como cámara. Funciona por **USB** (ADB) y **WiFi**, y cambia
|
||||
automáticamente si una se cae.
|
||||
|
||||
- Híbrido **USB + WiFi** con conmutación automática
|
||||
- **Se conecta automáticamente** cuando el teléfono está listo
|
||||
- Cambio de cámara **frontal / trasera**
|
||||
- **Vertical/horizontal automático**
|
||||
- **Vista previa en vivo** en el teléfono y el PC
|
||||
- Cámara **frontal / trasera**, **vertical/horizontal automático**
|
||||
- Sigue transmitiendo con la **pantalla apagada**
|
||||
- **10 idiomas**, interfaz oscura
|
||||
- **Código de acceso WiFi** — los desconocidos de tu red no ven la transmisión
|
||||
- **10 idiomas**, interfaz oscura, bandeja
|
||||
- Funciona totalmente en tu dispositivo — **ningún dato sale de tu ordenador**
|
||||
|
||||
## Controlador de cámara virtual
|
||||
|
||||
FlexCam necesita una cámara virtual en el PC. Tienes dos opciones:
|
||||
|
||||
- **Controlador incluido (sin OBS):** en el primer uso, si no se encuentra una
|
||||
cámara virtual, haz clic en **"Instalar cámara virtual"** en la app. Registra
|
||||
una vez un pequeño controlador de código abierto
|
||||
([Unity Capture](https://github.com/schellingb/UnityCapture), MIT), con una
|
||||
solicitud de administrador única.
|
||||
- **OBS Studio:** si ya tienes [OBS](https://obsproject.com/download), FlexCam
|
||||
usa su cámara virtual automáticamente.
|
||||
|
||||
## Inicio rápido
|
||||
|
||||
1. Instala **OBS Studio** (una vez).
|
||||
2. En el teléfono: instala `release/FlexCam.apk` y abre la app.
|
||||
3. En el PC: ejecuta **FlexCam** — se conecta solo.
|
||||
4. En tu app de video, elige la cámara **"OBS Virtual Camera"**.
|
||||
1. **Teléfono:** instala `FlexCam.apk` y abre la app.
|
||||
2. **PC:** descomprime **FlexCam-Windows.zip** y ejecuta `FlexCam.exe`.
|
||||
- Primer uso: si se pide, haz clic en **"Instalar cámara virtual"** (una vez).
|
||||
3. Se conecta por USB **automáticamente**. Para WiFi, escribe la IP del teléfono
|
||||
y el **código de acceso** que muestra la app.
|
||||
4. En tu app de video, elige la cámara **"OBS Virtual Camera"** (o
|
||||
**"Unity Video Capture"** con el controlador incluido).
|
||||
|
||||
> USB necesita la **depuración USB** activada (opciones de desarrollador). WiFi
|
||||
> no necesita cable.
|
||||
|
||||
## Compilar desde el código
|
||||
|
||||
**Android (APK):**
|
||||
```
|
||||
cd android
|
||||
gradlew assembleDebug
|
||||
```
|
||||
> Requiere JDK 17.
|
||||
|
||||
**PC (app de escritorio):**
|
||||
```
|
||||
cd pc
|
||||
python -m venv .venv
|
||||
.venv\Scripts\python -m pip install -r requirements.txt
|
||||
.venv\Scripts\python webgui.py
|
||||
```
|
||||
|
||||
`.exe` independiente con PyInstaller (onedir) — mira el comando en el README en inglés.
|
||||
|
||||
## Tecnología
|
||||
|
||||
- **Android:** Kotlin, CameraX, servidor MJPEG en el puerto `8474`.
|
||||
- **PC:** Python, interfaz pywebview, `pyvirtualcam` → OBS o Unity Capture.
|
||||
- **Transporte:** TCP puro vía `adb forward` (USB) o la IP del teléfono (WiFi).
|
||||
|
||||
## Licencia
|
||||
|
||||
|
||||
@@ -16,34 +16,72 @@
|
||||
|
||||
---
|
||||
|
||||
> [!CAUTION]
|
||||
> **FlexCam nécessite [OBS Studio](https://obsproject.com/download).**
|
||||
> Il utilise la caméra virtuelle d'OBS pour exposer votre téléphone comme webcam.
|
||||
> Installez OBS une fois — pas besoin de le garder ouvert.
|
||||
|
||||
---
|
||||
|
||||
## Qu'est-ce que c'est
|
||||
|
||||
FlexCam diffuse la caméra de votre téléphone Android vers votre PC et l'expose
|
||||
comme **« OBS Virtual Camera »**, afin que n'importe quelle app (Zoom, Teams,
|
||||
Discord, navigateur, OBS) puisse la choisir comme webcam. Fonctionne via **USB**
|
||||
comme une **webcam virtuelle**, afin que n'importe quelle app (Zoom, Teams,
|
||||
Discord, navigateur, OBS) puisse la choisir comme caméra. Fonctionne via **USB**
|
||||
(ADB) et **WiFi**, et bascule automatiquement si l'une tombe.
|
||||
|
||||
- Hybride **USB + WiFi** avec bascule automatique
|
||||
- **Se connecte automatiquement** quand le téléphone est prêt
|
||||
- Bascule caméra **avant / arrière**
|
||||
- **Portrait/paysage automatique**
|
||||
- **Aperçu en direct** sur le téléphone et le PC
|
||||
- Caméra **avant / arrière**, **portrait/paysage automatique**
|
||||
- Continue le flux **écran éteint**
|
||||
- **10 langues**, interface sombre
|
||||
- **Code d'accès WiFi** — les inconnus du réseau ne peuvent pas voir le flux
|
||||
- **10 langues**, interface sombre, zone de notification
|
||||
- Fonctionne entièrement sur votre appareil — **aucune donnée ne sort**
|
||||
|
||||
## Pilote de caméra virtuelle
|
||||
|
||||
FlexCam a besoin d'une caméra virtuelle sur le PC. Deux options :
|
||||
|
||||
- **Pilote inclus (sans OBS) :** au premier lancement, si aucune caméra
|
||||
virtuelle n'est trouvée, cliquez sur **« Installer la caméra virtuelle »**
|
||||
dans l'app. Il enregistre une fois un petit pilote open-source
|
||||
([Unity Capture](https://github.com/schellingb/UnityCapture), MIT), avec une
|
||||
demande admin unique.
|
||||
- **OBS Studio :** si vous avez déjà [OBS](https://obsproject.com/download),
|
||||
FlexCam utilise sa caméra virtuelle automatiquement.
|
||||
|
||||
## Démarrage rapide
|
||||
|
||||
1. Installez **OBS Studio** (une fois).
|
||||
2. Sur le téléphone : installez `release/FlexCam.apk` et ouvrez l'app.
|
||||
3. Sur le PC : lancez **FlexCam** — il se connecte tout seul.
|
||||
4. Dans votre app vidéo, choisissez la caméra **« OBS Virtual Camera »**.
|
||||
1. **Téléphone :** installez `FlexCam.apk` et ouvrez l'app.
|
||||
2. **PC :** décompressez **FlexCam-Windows.zip** et lancez `FlexCam.exe`.
|
||||
- Premier lancement : si demandé, cliquez sur **« Installer la caméra
|
||||
virtuelle »** (une fois).
|
||||
3. Se connecte en USB **automatiquement**. Pour le WiFi, saisissez l'IP du
|
||||
téléphone et le **code d'accès** affiché dans l'app.
|
||||
4. Dans votre app vidéo, choisissez la caméra **« OBS Virtual Camera »** (ou
|
||||
**« Unity Video Capture »** avec le pilote inclus).
|
||||
|
||||
> L'USB nécessite le **débogage USB** activé (options développeur). Le WiFi ne
|
||||
> nécessite pas de câble.
|
||||
|
||||
## Compiler depuis les sources
|
||||
|
||||
**Android (APK):**
|
||||
```
|
||||
cd android
|
||||
gradlew assembleDebug
|
||||
```
|
||||
> Nécessite JDK 17.
|
||||
|
||||
**PC (application de bureau):**
|
||||
```
|
||||
cd pc
|
||||
python -m venv .venv
|
||||
.venv\Scripts\python -m pip install -r requirements.txt
|
||||
.venv\Scripts\python webgui.py
|
||||
```
|
||||
|
||||
`.exe` autonome avec PyInstaller (onedir) — voir la commande dans le README anglais.
|
||||
|
||||
## Technique
|
||||
|
||||
- **Android :** Kotlin, CameraX, serveur MJPEG sur le port `8474`.
|
||||
- **PC :** Python, interface pywebview, `pyvirtualcam` → OBS ou Unity Capture.
|
||||
- **Transport :** TCP brut via `adb forward` (USB) ou l'IP du téléphone (WiFi).
|
||||
|
||||
## Licence
|
||||
|
||||
|
||||
@@ -16,34 +16,71 @@
|
||||
|
||||
---
|
||||
|
||||
> [!CAUTION]
|
||||
> **FlexCam के लिए [OBS Studio](https://obsproject.com/download) इंस्टॉल होना ज़रूरी है।**
|
||||
> यह आपके फ़ोन को वेबकैम के रूप में दिखाने के लिए OBS Virtual Camera का उपयोग करता है।
|
||||
> OBS एक बार इंस्टॉल करें — इसे खुला रखने की ज़रूरत नहीं।
|
||||
|
||||
---
|
||||
|
||||
## यह क्या है
|
||||
|
||||
FlexCam आपके Android फ़ोन के कैमरे को PC पर स्ट्रीम करता है और उसे
|
||||
**"OBS Virtual Camera"** के रूप में दिखाता है, ताकि कोई भी ऐप (Zoom, Teams,
|
||||
Discord, ब्राउज़र, OBS) उसे वेबकैम के रूप में चुन सके। यह **USB** (ADB) और
|
||||
**WiFi** पर काम करता है, और एक के टूटने पर अपने आप दूसरे पर चला जाता है।
|
||||
FlexCam आपके Android फ़ोन के कैमरे को PC पर स्ट्रीम करता है और उसे एक
|
||||
**वर्चुअल वेबकैम** के रूप में दिखाता है, ताकि कोई भी ऐप (Zoom, Teams, Discord,
|
||||
ब्राउज़र, OBS) उसे कैमरे के रूप में चुन सके। यह **USB** (ADB) और **WiFi** पर काम
|
||||
करता है, और एक के टूटने पर अपने आप दूसरे पर चला जाता है।
|
||||
|
||||
- हाइब्रिड **USB + WiFi**, स्वतः फ़ेलओवर
|
||||
- फ़ोन तैयार होने पर **स्वतः कनेक्ट**
|
||||
- **आगे / पीछे** कैमरा स्विच
|
||||
- **स्वतः पोर्ट्रेट/लैंडस्केप**
|
||||
- फ़ोन और PC दोनों पर **लाइव प्रीव्यू**
|
||||
- **आगे / पीछे** कैमरा, **स्वतः पोर्ट्रेट/लैंडस्केप**
|
||||
- **स्क्रीन बंद** होने पर भी स्ट्रीमिंग जारी
|
||||
- **10 भाषाएँ**, डार्क UI
|
||||
- **WiFi एक्सेस कोड** — आपके नेटवर्क के अजनबी स्ट्रीम नहीं देख सकते
|
||||
- **10 भाषाएँ**, डार्क UI, ट्रे समर्थन
|
||||
- पूरी तरह आपके डिवाइस पर चलता है — **कोई डेटा बाहर नहीं जाता**
|
||||
|
||||
## वर्चुअल कैमरा ड्राइवर
|
||||
|
||||
FlexCam को PC पर एक वर्चुअल कैमरा चाहिए। आपके पास दो विकल्प हैं:
|
||||
|
||||
- **साथ में शामिल ड्राइवर (OBS की ज़रूरत नहीं):** पहली बार चलाने पर अगर कोई
|
||||
वर्चुअल कैमरा न मिले, तो ऐप में **"वर्चुअल कैमरा इंस्टॉल करें"** पर क्लिक करें।
|
||||
यह एक छोटा ओपन-सोर्स ड्राइवर
|
||||
([Unity Capture](https://github.com/schellingb/UnityCapture), MIT) एक बार,
|
||||
एक व्यवस्थापक अनुमति के साथ, रजिस्टर करता है।
|
||||
- **OBS Studio:** अगर आपके पास पहले से [OBS](https://obsproject.com/download) है,
|
||||
तो FlexCam उसका वर्चुअल कैमरा अपने आप उपयोग करता है।
|
||||
|
||||
## जल्दी शुरू करें
|
||||
|
||||
1. **OBS Studio** इंस्टॉल करें (एक बार)।
|
||||
2. फ़ोन पर: `release/FlexCam.apk` इंस्टॉल करें और ऐप खोलें।
|
||||
3. PC पर: **FlexCam** चलाएँ — यह अपने आप कनेक्ट हो जाता है।
|
||||
4. अपने वीडियो ऐप में कैमरा **"OBS Virtual Camera"** चुनें।
|
||||
1. **फ़ोन:** `FlexCam.apk` इंस्टॉल करें और ऐप खोलें।
|
||||
2. **PC:** **FlexCam-Windows.zip** को अनज़िप करें और `FlexCam.exe` चलाएँ।
|
||||
- पहली बार: पूछे जाने पर **"वर्चुअल कैमरा इंस्टॉल करें"** पर क्लिक करें (एक बार)।
|
||||
3. USB से **स्वतः कनेक्ट** होता है। WiFi के लिए फ़ोन का IP और ऐप में दिखाया गया
|
||||
**एक्सेस कोड** लिखें।
|
||||
4. अपने वीडियो ऐप में कैमरा **"OBS Virtual Camera"** (शामिल ड्राइवर के साथ
|
||||
**"Unity Video Capture"**) चुनें।
|
||||
|
||||
> USB के लिए **USB डिबगिंग** चालू होना चाहिए (डेवलपर विकल्प)। WiFi को केबल की
|
||||
> ज़रूरत नहीं।
|
||||
|
||||
## सोर्स से बनाएँ
|
||||
|
||||
**Android (APK):**
|
||||
```
|
||||
cd android
|
||||
gradlew assembleDebug
|
||||
```
|
||||
> JDK 17 आवश्यक है।
|
||||
|
||||
**PC (डेस्कटॉप ऐप):**
|
||||
```
|
||||
cd pc
|
||||
python -m venv .venv
|
||||
.venv\Scripts\python -m pip install -r requirements.txt
|
||||
.venv\Scripts\python webgui.py
|
||||
```
|
||||
|
||||
PyInstaller (onedir) से स्टैंडअलोन `.exe` — अंग्रेज़ी README में कमांड देखें।
|
||||
|
||||
## तकनीक
|
||||
|
||||
- **Android:** Kotlin, CameraX, पोर्ट `8474` पर MJPEG सर्वर।
|
||||
- **PC:** Python, pywebview UI, `pyvirtualcam` → OBS या Unity Capture।
|
||||
- **ट्रांसपोर्ट:** `adb forward` (USB) या फ़ोन के IP (WiFi) पर रॉ TCP।
|
||||
|
||||
## लाइसेंस
|
||||
|
||||
|
||||
@@ -16,34 +16,70 @@
|
||||
|
||||
---
|
||||
|
||||
> [!CAUTION]
|
||||
> **FlexCam には [OBS Studio](https://obsproject.com/download) が必要です。**
|
||||
> スマホをウェブカメラとして見せるために OBS 仮想カメラを使います。
|
||||
> OBS は一度インストールするだけ — 開いたままにする必要はありません。
|
||||
|
||||
---
|
||||
|
||||
## これは何
|
||||
|
||||
FlexCam は Android スマホのカメラを PC にストリーミングし、**「OBS Virtual
|
||||
Camera」** として見せます。これにより、どのアプリ(Zoom、Teams、Discord、
|
||||
ブラウザ、OBS)でもウェブカメラとして選べます。**USB**(ADB)と **WiFi** で
|
||||
動作し、片方が切れると自動で切り替わります。
|
||||
FlexCam は Android スマホのカメラを PC にストリーミングし、**仮想ウェブカメラ**
|
||||
として見せます。これにより、どのアプリ(Zoom、Teams、Discord、ブラウザ、OBS)
|
||||
でもカメラとして選べます。**USB**(ADB)と **WiFi** で動作し、片方が切れると
|
||||
自動で切り替わります。
|
||||
|
||||
- **USB + WiFi** ハイブリッド、自動フェイルオーバー
|
||||
- スマホの準備ができると**自動接続**
|
||||
- **前面 / 背面**カメラ切り替え
|
||||
- **自動で縦/横**
|
||||
- スマホと PC の両方で**ライブプレビュー**
|
||||
- **前面 / 背面**カメラ、**自動で縦/横**
|
||||
- **画面オフ**でも配信継続
|
||||
- **10 言語**、ダーク UI
|
||||
- **WiFi アクセスコード** — ネットワーク上の他人は映像を見られません
|
||||
- **10 言語**、ダーク UI、トレイ対応
|
||||
- すべて端末内で動作 — **データがパソコンから出ることはありません**
|
||||
|
||||
## 仮想カメラドライバー
|
||||
|
||||
FlexCam は PC 上の仮想カメラが必要です。2 つの方法があります:
|
||||
|
||||
- **同梱ドライバー(OBS 不要):** 初回起動時に仮想カメラが見つからない場合、
|
||||
アプリの **「仮想カメラをインストール」** をクリック。小さなオープンソース
|
||||
ドライバー([Unity Capture](https://github.com/schellingb/UnityCapture)、MIT)
|
||||
を一度だけ、管理者確認付きで登録します。
|
||||
- **OBS Studio:** すでに [OBS](https://obsproject.com/download) がある場合、
|
||||
FlexCam はその仮想カメラを自動的に使います。
|
||||
|
||||
## クイックスタート
|
||||
|
||||
1. **OBS Studio** をインストール(一度)。
|
||||
2. スマホ:`release/FlexCam.apk` をインストールしてアプリを開く。
|
||||
3. PC:**FlexCam** を起動 — 自動で接続します。
|
||||
4. ビデオアプリでカメラ **「OBS Virtual Camera」** を選択。
|
||||
1. **スマホ:** `FlexCam.apk` をインストールしてアプリを開く。
|
||||
2. **PC:** **FlexCam-Windows.zip** を解凍し `FlexCam.exe` を起動。
|
||||
- 初回:求められたら **「仮想カメラをインストール」**(一度だけ)。
|
||||
3. USB で**自動接続**します。WiFi の場合はスマホの IP とアプリに表示される
|
||||
**アクセスコード**を入力。
|
||||
4. ビデオアプリでカメラ **「OBS Virtual Camera」**(同梱ドライバー使用時は
|
||||
**「Unity Video Capture」**)を選択。
|
||||
|
||||
> USB は**USB デバッグ**の有効化が必要(開発者向けオプション)。WiFi はケーブル
|
||||
> 不要。
|
||||
|
||||
## ソースからビルド
|
||||
|
||||
**Android (APK):**
|
||||
```
|
||||
cd android
|
||||
gradlew assembleDebug
|
||||
```
|
||||
> JDK 17 が必要。
|
||||
|
||||
**PC(デスクトップアプリ):**
|
||||
```
|
||||
cd pc
|
||||
python -m venv .venv
|
||||
.venv\Scripts\python -m pip install -r requirements.txt
|
||||
.venv\Scripts\python webgui.py
|
||||
```
|
||||
|
||||
PyInstaller(onedir)でスタンドアロン `.exe` — 英語 README のコマンドを参照。
|
||||
|
||||
## 技術
|
||||
|
||||
- **Android:** Kotlin、CameraX、ポート `8474` の MJPEG サーバー。
|
||||
- **PC:** Python、pywebview UI、`pyvirtualcam` → OBS または Unity Capture。
|
||||
- **転送:** `adb forward`(USB)またはスマホの IP(WiFi)経由の生 TCP。
|
||||
|
||||
## ライセンス
|
||||
|
||||
|
||||
@@ -16,34 +16,71 @@
|
||||
|
||||
---
|
||||
|
||||
> [!CAUTION]
|
||||
> **O FlexCam requer o [OBS Studio](https://obsproject.com/download).**
|
||||
> Ele usa a OBS Virtual Camera para expor seu telefone como webcam.
|
||||
> Instale o OBS uma vez — não precisa mantê-lo aberto.
|
||||
|
||||
---
|
||||
|
||||
## O que é
|
||||
|
||||
O FlexCam transmite a câmera do seu telefone Android para o PC e a expõe como
|
||||
**"OBS Virtual Camera"**, para que qualquer app (Zoom, Teams, Discord, navegador,
|
||||
OBS) possa escolhê-la como webcam. Funciona via **USB** (ADB) e **WiFi**, e
|
||||
alterna automaticamente se uma cair.
|
||||
O FlexCam transmite a câmera do seu telefone Android para o PC e a expõe como uma
|
||||
**webcam virtual**, para que qualquer app (Zoom, Teams, Discord, navegador, OBS)
|
||||
possa escolhê-la como câmera. Funciona via **USB** (ADB) e **WiFi**, e alterna
|
||||
automaticamente se uma cair.
|
||||
|
||||
- Híbrido **USB + WiFi** com troca automática
|
||||
- **Conecta automaticamente** quando o telefone está pronto
|
||||
- Troca de câmera **frontal / traseira**
|
||||
- **Retrato/paisagem automático**
|
||||
- **Pré-visualização ao vivo** no telefone e no PC
|
||||
- Câmera **frontal / traseira**, **retrato/paisagem automático**
|
||||
- Continua transmitindo com a **tela desligada**
|
||||
- **10 idiomas**, interface escura
|
||||
- **Código de acesso WiFi** — estranhos na sua rede não veem a transmissão
|
||||
- **10 idiomas**, interface escura, bandeja
|
||||
- Roda totalmente no seu dispositivo — **nenhum dado sai do seu computador**
|
||||
|
||||
## Driver de câmera virtual
|
||||
|
||||
O FlexCam precisa de uma câmera virtual no PC. Você tem duas opções:
|
||||
|
||||
- **Driver incluído (sem OBS):** no primeiro uso, se nenhuma câmera virtual for
|
||||
encontrada, clique em **"Instalar câmera virtual"** no app. Ele registra uma
|
||||
vez um pequeno driver de código aberto
|
||||
([Unity Capture](https://github.com/schellingb/UnityCapture), MIT), com um
|
||||
pedido de administrador único.
|
||||
- **OBS Studio:** se você já tem o [OBS](https://obsproject.com/download), o
|
||||
FlexCam usa a câmera virtual dele automaticamente.
|
||||
|
||||
## Início rápido
|
||||
|
||||
1. Instale o **OBS Studio** (uma vez).
|
||||
2. No telefone: instale `release/FlexCam.apk` e abra o app.
|
||||
3. No PC: execute o **FlexCam** — ele conecta sozinho.
|
||||
4. No seu app de vídeo, escolha a câmera **"OBS Virtual Camera"**.
|
||||
1. **Telefone:** instale `FlexCam.apk` e abra o app.
|
||||
2. **PC:** extraia **FlexCam-Windows.zip** e execute `FlexCam.exe`.
|
||||
- Primeiro uso: se solicitado, clique em **"Instalar câmera virtual"** (uma vez).
|
||||
3. Conecta por USB **automaticamente**. Para WiFi, digite o IP do telefone e o
|
||||
**código de acesso** mostrado no app.
|
||||
4. No seu app de vídeo, escolha a câmera **"OBS Virtual Camera"** (ou
|
||||
**"Unity Video Capture"** com o driver incluído).
|
||||
|
||||
> USB precisa de **depuração USB** ativada (Opções do desenvolvedor). WiFi não
|
||||
> precisa de cabo.
|
||||
|
||||
## Compilar do código-fonte
|
||||
|
||||
**Android (APK):**
|
||||
```
|
||||
cd android
|
||||
gradlew assembleDebug
|
||||
```
|
||||
> Requer JDK 17.
|
||||
|
||||
**PC (app de desktop):**
|
||||
```
|
||||
cd pc
|
||||
python -m venv .venv
|
||||
.venv\Scripts\python -m pip install -r requirements.txt
|
||||
.venv\Scripts\python webgui.py
|
||||
```
|
||||
|
||||
`.exe` independente com PyInstaller (onedir) — veja o comando no README em inglês.
|
||||
|
||||
## Tecnologia
|
||||
|
||||
- **Android:** Kotlin, CameraX, servidor MJPEG na porta `8474`.
|
||||
- **PC:** Python, interface pywebview, `pyvirtualcam` → OBS ou Unity Capture.
|
||||
- **Transporte:** TCP puro via `adb forward` (USB) ou o IP do telefone (WiFi).
|
||||
|
||||
## Licença
|
||||
|
||||
|
||||
@@ -16,37 +16,71 @@
|
||||
|
||||
---
|
||||
|
||||
> [!CAUTION]
|
||||
> **FlexCam'in çalışması için [OBS Studio](https://obsproject.com/download) kurulu olmalıdır.**
|
||||
> Telefonu webcam olarak göstermek için OBS Virtual Camera sürücüsünü kullanır.
|
||||
> OBS'i bir kez kur — açık tutmana gerek yok.
|
||||
|
||||
---
|
||||
|
||||
## Nedir
|
||||
|
||||
FlexCam, Android telefonunun kamerasını PC'ne aktarır ve **"OBS Virtual Camera"**
|
||||
FlexCam, Android telefonunun kamerasını PC'ne aktarır ve bir **sanal webcam**
|
||||
olarak gösterir; böylece herhangi bir uygulama (Zoom, Teams, Discord, tarayıcı,
|
||||
OBS) onu webcam olarak seçebilir. **USB** (ADB) ve **WiFi** üzerinden çalışır,
|
||||
OBS) onu kamera olarak seçebilir. **USB** (ADB) ve **WiFi** üzerinden çalışır,
|
||||
biri koparsa otomatik olarak diğerine geçer.
|
||||
|
||||
- Hibrit **USB + WiFi**, otomatik yük devretme
|
||||
- Telefon hazır olunca **otomatik bağlanır** — elle işlem yok
|
||||
- **Ön / arka** kamera geçişi
|
||||
- **Otomatik dik/yatay** (telefonun yönünü izler)
|
||||
- Telefonda ve PC'de **canlı önizleme**
|
||||
- **Ön / arka** kamera geçişi, **otomatik dik/yatay**
|
||||
- **Ekran kapalıyken** de yayın sürer
|
||||
- **WiFi erişim kodu** — ağdaki yabancılar yayınını izleyemez
|
||||
- **10 dil**, koyu arayüz, tepsi desteği
|
||||
- Tamamen cihazında çalışır — **hiçbir veri dışarı gönderilmez**
|
||||
|
||||
## Sanal kamera sürücüsü
|
||||
|
||||
FlexCam'in PC'de bir sanal kameraya ihtiyacı var. İki seçeneğin var:
|
||||
|
||||
- **Gömülü sürücü (OBS gerekmez):** İlk açılışta sanal kamera bulunamazsa,
|
||||
uygulamadaki **"Sanal kamera kur"** düğmesine tıkla. Küçük, açık kaynak bir
|
||||
sürücüyü ([Unity Capture](https://github.com/schellingb/UnityCapture), MIT)
|
||||
tek seferlik yönetici onayıyla kaydeder.
|
||||
- **OBS Studio:** Zaten [OBS](https://obsproject.com/download) kuruluysa FlexCam
|
||||
onun sanal kamerasını otomatik kullanır.
|
||||
|
||||
## Hızlı başlangıç
|
||||
|
||||
1. **OBS Studio** kur (bir kez).
|
||||
2. Telefonda: `release/FlexCam.apk` kur ve uygulamayı aç.
|
||||
3. PC'de: **FlexCam**'i çalıştır, otomatik bağlanır.
|
||||
4. Video uygulamanda kamera olarak **"OBS Virtual Camera"** seç.
|
||||
1. **Telefon:** `FlexCam.apk`'yı kur ve uygulamayı aç.
|
||||
2. **PC:** **FlexCam-Windows.zip**'i aç ve `FlexCam.exe`'yi çalıştır.
|
||||
- İlk açılış: istenirse **"Sanal kamera kur"**a tıkla (tek seferlik).
|
||||
3. USB üzerinden **otomatik bağlanır**. WiFi için telefonun IP'sini ve
|
||||
uygulamada görünen **erişim kodunu** yaz.
|
||||
4. Video uygulamanda kamera olarak **"OBS Virtual Camera"** (gömülü sürücü
|
||||
kullandıysan **"Unity Video Capture"**) seç.
|
||||
|
||||
USB için **USB hata ayıklama** açık olmalı (Geliştirici Seçenekleri). WiFi kablo
|
||||
istemez — uygulamanın gösterdiği IP'yi yaz.
|
||||
> USB için **USB hata ayıklama** açık olmalı (Geliştirici Seçenekleri).
|
||||
> WiFi kablo istemez.
|
||||
|
||||
## Kaynaktan derleme
|
||||
|
||||
**Android (APK):**
|
||||
```
|
||||
cd android
|
||||
gradlew assembleDebug
|
||||
```
|
||||
> JDK 17 gerekir.
|
||||
|
||||
**PC (masaüstü uygulaması):**
|
||||
```
|
||||
cd pc
|
||||
python -m venv .venv
|
||||
.venv\Scripts\python -m pip install -r requirements.txt
|
||||
.venv\Scripts\python webgui.py
|
||||
```
|
||||
|
||||
Bağımsız `.exe` paketlemek (PyInstaller, onedir): İngilizce README'deki komuta
|
||||
bak.
|
||||
|
||||
## Teknik
|
||||
|
||||
- **Android:** Kotlin, CameraX, `8474` portunda MJPEG sunucu.
|
||||
- **PC:** Python, pywebview arayüz, `pyvirtualcam` → OBS veya Unity Capture.
|
||||
- **Aktarım:** `adb forward` (USB) ya da telefonun IP'si (WiFi) üzerinden ham TCP.
|
||||
|
||||
## Lisans
|
||||
|
||||
|
||||
@@ -16,33 +16,67 @@
|
||||
|
||||
---
|
||||
|
||||
> [!CAUTION]
|
||||
> **FlexCam 需要安装 [OBS Studio](https://obsproject.com/download)。**
|
||||
> 它使用 OBS 虚拟摄像头把手机显示为网络摄像头。
|
||||
> 安装一次 OBS 即可 — 无需保持打开。
|
||||
|
||||
---
|
||||
|
||||
## 这是什么
|
||||
|
||||
FlexCam 把安卓手机的摄像头串流到电脑,并显示为 **“OBS Virtual Camera”**,
|
||||
这样任何应用(Zoom、Teams、Discord、浏览器、OBS)都能把它当作网络摄像头。
|
||||
FlexCam 把安卓手机的摄像头串流到电脑,并显示为一个**虚拟网络摄像头**,
|
||||
这样任何应用(Zoom、Teams、Discord、浏览器、OBS)都能把它当作摄像头。
|
||||
它通过 **USB**(ADB)和 **WiFi** 工作,其中一个断开时会自动切换。
|
||||
|
||||
- **USB + WiFi** 混合,自动切换
|
||||
- 手机就绪时**自动连接**
|
||||
- **前置 / 后置**摄像头切换
|
||||
- **自动竖屏/横屏**
|
||||
- 手机和电脑上都有**实时预览**
|
||||
- **前置 / 后置**摄像头,**自动竖屏/横屏**
|
||||
- **息屏**时继续串流
|
||||
- **10 种语言**,深色界面
|
||||
- **WiFi 访问码** — 网络中的陌生人无法查看你的画面
|
||||
- **10 种语言**,深色界面,托盘支持
|
||||
- 完全在你的设备上运行 — **没有任何数据离开你的电脑**
|
||||
|
||||
## 虚拟摄像头驱动
|
||||
|
||||
FlexCam 需要电脑上的虚拟摄像头。你有两个选择:
|
||||
|
||||
- **内置驱动(无需 OBS):** 首次运行时如果未找到虚拟摄像头,点击应用中的
|
||||
**“安装虚拟摄像头”**。它会一次性注册一个小型开源驱动
|
||||
([Unity Capture](https://github.com/schellingb/UnityCapture),MIT),
|
||||
需要一次管理员确认。
|
||||
- **OBS Studio:** 如果你已安装 [OBS](https://obsproject.com/download),
|
||||
FlexCam 会自动使用它的虚拟摄像头。
|
||||
|
||||
## 快速开始
|
||||
|
||||
1. 安装 **OBS Studio**(一次)。
|
||||
2. 手机上:安装 `release/FlexCam.apk` 并打开应用。
|
||||
3. 电脑上:运行 **FlexCam** — 它会自动连接。
|
||||
4. 在你的视频应用中,选择摄像头 **“OBS Virtual Camera”**。
|
||||
1. **手机:** 安装 `FlexCam.apk` 并打开应用。
|
||||
2. **电脑:** 解压 **FlexCam-Windows.zip** 并运行 `FlexCam.exe`。
|
||||
- 首次运行:如有提示,点击**“安装虚拟摄像头”**(仅一次)。
|
||||
3. 通过 USB **自动连接**。使用 WiFi 时,输入手机 IP 和应用中显示的**访问码**。
|
||||
4. 在你的视频应用中,选择摄像头 **“OBS Virtual Camera”**
|
||||
(若使用内置驱动则为 **“Unity Video Capture”**)。
|
||||
|
||||
> USB 需要开启**USB 调试**(开发者选项)。WiFi 不需要线缆。
|
||||
|
||||
## 从源码构建
|
||||
|
||||
**Android (APK):**
|
||||
```
|
||||
cd android
|
||||
gradlew assembleDebug
|
||||
```
|
||||
> 需要 JDK 17。
|
||||
|
||||
**PC(桌面应用):**
|
||||
```
|
||||
cd pc
|
||||
python -m venv .venv
|
||||
.venv\Scripts\python -m pip install -r requirements.txt
|
||||
.venv\Scripts\python webgui.py
|
||||
```
|
||||
|
||||
用 PyInstaller(onedir)打包独立 `.exe` — 见英文 README 中的命令。
|
||||
|
||||
## 技术
|
||||
|
||||
- **Android:** Kotlin、CameraX,端口 `8474` 上的 MJPEG 服务器。
|
||||
- **PC:** Python、pywebview 界面、`pyvirtualcam` → OBS 或 Unity Capture。
|
||||
- **传输:** 通过 `adb forward`(USB)或手机 IP(WiFi)的原始 TCP。
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
29
pc/engine.py
29
pc/engine.py
@@ -9,9 +9,13 @@ import adb_tools
|
||||
PORT = 8474
|
||||
|
||||
|
||||
def _stream(host, timeout=2):
|
||||
def _q(key):
|
||||
return f"?key={key}" if key else ""
|
||||
|
||||
|
||||
def _stream(host, key="", timeout=2):
|
||||
conn = HTTPConnection(host, PORT, timeout=timeout)
|
||||
conn.request("GET", "/")
|
||||
conn.request("GET", "/" + _q(key))
|
||||
resp = conn.getresponse()
|
||||
if resp.status != 200:
|
||||
raise RuntimeError(f"HTTP {resp.status}")
|
||||
@@ -25,10 +29,10 @@ def _stream(host, timeout=2):
|
||||
yield jpeg
|
||||
|
||||
|
||||
def _control(host, cmd):
|
||||
def _control(host, cmd, key=""):
|
||||
try:
|
||||
conn = HTTPConnection(host, PORT, timeout=1.5)
|
||||
conn.request("GET", "/" + cmd)
|
||||
conn.request("GET", "/" + cmd + _q(key))
|
||||
conn.getresponse().read()
|
||||
conn.close()
|
||||
except Exception:
|
||||
@@ -42,13 +46,21 @@ class FlexCamEngine:
|
||||
self._thread = None
|
||||
self._running = False
|
||||
self._active_host = None
|
||||
self._key = ""
|
||||
self._preview_jpeg = None
|
||||
|
||||
def is_running(self):
|
||||
return self._running
|
||||
|
||||
def preview_jpeg(self):
|
||||
return self._preview_jpeg
|
||||
|
||||
def set_wifi_ip(self, ip):
|
||||
self._wifi_ip = ip
|
||||
|
||||
def set_key(self, key):
|
||||
self._key = (key or "").strip()
|
||||
|
||||
def start(self):
|
||||
if self._running:
|
||||
return
|
||||
@@ -95,26 +107,29 @@ class FlexCamEngine:
|
||||
if src["usb"]:
|
||||
if not adb_tools.ensure_forward():
|
||||
continue
|
||||
_control(src["host"], "start")
|
||||
for jpeg in _stream(src["host"]):
|
||||
_control(src["host"], "start", self._key)
|
||||
for jpeg in _stream(src["host"], self._key):
|
||||
if not self._running:
|
||||
break
|
||||
if active != src["name"]:
|
||||
active = src["name"]
|
||||
self._active_host = src["host"]
|
||||
self._on_status("connected", active, "")
|
||||
self._preview_jpeg = jpeg
|
||||
cam.send(jpeg_to_rgb(jpeg))
|
||||
active = None
|
||||
self._preview_jpeg = None
|
||||
except Exception:
|
||||
if active == src["name"]:
|
||||
self._on_status("searching", None, "")
|
||||
active = None
|
||||
self._preview_jpeg = None
|
||||
if self._running:
|
||||
time.sleep(1)
|
||||
finally:
|
||||
cam.close()
|
||||
if self._active_host:
|
||||
_control(self._active_host, "stop")
|
||||
_control(self._active_host, "stop", self._key)
|
||||
self._active_host = None
|
||||
self._running = False
|
||||
self._on_status("stopped", None, "")
|
||||
|
||||
@@ -38,6 +38,9 @@ STRINGS = {
|
||||
"install_obs": "Install OBS",
|
||||
"obs_ready": "OBS ready",
|
||||
"obs_missing": "OBS Studio is required",
|
||||
"vcam_ready": "Virtual camera ready",
|
||||
"install_vcam": "Install virtual camera",
|
||||
"vcam_installing": "Installing driver — approve the admin prompt.",
|
||||
"phone_ready": "Phone app ready",
|
||||
"settings": "Settings",
|
||||
"setting_tray": "Minimize to tray on close",
|
||||
@@ -74,6 +77,9 @@ STRINGS = {
|
||||
"install_obs": "OBS'yi Kur",
|
||||
"obs_ready": "OBS hazır",
|
||||
"obs_missing": "OBS Studio gerekli",
|
||||
"vcam_ready": "Sanal kamera hazır",
|
||||
"install_vcam": "Sanal kamera kur",
|
||||
"vcam_installing": "Sürücü kuruluyor — yönetici iznini onayla.",
|
||||
"phone_ready": "Uygulama kurulu",
|
||||
"settings": "Ayarlar",
|
||||
"setting_tray": "Kapatınca tepsiye küçült",
|
||||
|
||||
54
pc/preview_server.py
Normal file
54
pc/preview_server.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import threading
|
||||
import time
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
PREVIEW_PORT = 8475
|
||||
|
||||
|
||||
class PreviewServer:
|
||||
def __init__(self, get_frame):
|
||||
self._get = get_frame
|
||||
self._httpd = None
|
||||
|
||||
def start(self):
|
||||
get = self._get
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *a):
|
||||
pass
|
||||
|
||||
def do_GET(self):
|
||||
if self.path.startswith("/preview"):
|
||||
self._stream()
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
|
||||
def _stream(self):
|
||||
try:
|
||||
self.send_response(200)
|
||||
self.send_header(
|
||||
"Content-Type",
|
||||
"multipart/x-mixed-replace; boundary=frame",
|
||||
)
|
||||
self.send_header("Cache-Control", "no-cache")
|
||||
self.end_headers()
|
||||
while True:
|
||||
jpeg = get()
|
||||
if jpeg:
|
||||
self.wfile.write(b"--frame\r\n")
|
||||
self.wfile.write(b"Content-Type: image/jpeg\r\n")
|
||||
self.wfile.write(
|
||||
b"Content-Length: " + str(len(jpeg)).encode() + b"\r\n\r\n"
|
||||
)
|
||||
self.wfile.write(jpeg)
|
||||
self.wfile.write(b"\r\n")
|
||||
time.sleep(0.05)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
self._httpd = ThreadingHTTPServer(("127.0.0.1", PREVIEW_PORT), Handler)
|
||||
threading.Thread(target=self._httpd.serve_forever, daemon=True).start()
|
||||
except Exception:
|
||||
pass
|
||||
@@ -1,11 +1,34 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import webbrowser
|
||||
from urllib.request import urlopen
|
||||
|
||||
import config
|
||||
|
||||
|
||||
def _base_dir():
|
||||
if getattr(sys, "frozen", False):
|
||||
return getattr(sys, "_MEIPASS", os.path.dirname(sys.executable))
|
||||
return os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def virtualcam_installer():
|
||||
p = os.path.join(_base_dir(), "virtualcam", "Install.bat")
|
||||
return p if os.path.exists(p) else None
|
||||
|
||||
|
||||
def install_virtualcam():
|
||||
p = virtualcam_installer()
|
||||
if not p:
|
||||
return False
|
||||
try:
|
||||
os.startfile(p)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def open_url(url):
|
||||
try:
|
||||
webbrowser.open(url)
|
||||
|
||||
@@ -26,7 +26,17 @@ class VirtualCamera:
|
||||
def __init__(self, width: int = 1280, height: int = 720, fps: int = 30):
|
||||
self._w = width
|
||||
self._h = height
|
||||
self._cam = pyvirtualcam.Camera(width=width, height=height, fps=fps)
|
||||
self._cam = None
|
||||
for backend in ("obs", "unitycapture"):
|
||||
try:
|
||||
self._cam = pyvirtualcam.Camera(
|
||||
width=width, height=height, fps=fps, backend=backend
|
||||
)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if self._cam is None:
|
||||
self._cam = pyvirtualcam.Camera(width=width, height=height, fps=fps)
|
||||
|
||||
@property
|
||||
def device(self) -> str:
|
||||
|
||||
28
pc/virtualcam/Install.bat
Normal file
28
pc/virtualcam/Install.bat
Normal file
@@ -0,0 +1,28 @@
|
||||
@echo off
|
||||
|
||||
:: BatchGotAdmin
|
||||
:-------------------------------------
|
||||
REM --> Check for permissions
|
||||
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
|
||||
|
||||
REM --> If error flag set, we do not have admin.
|
||||
if '%errorlevel%' NEQ '0' (
|
||||
echo Requesting administrative privileges...
|
||||
goto UACPrompt
|
||||
) else ( goto gotAdmin )
|
||||
|
||||
:UACPrompt
|
||||
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
|
||||
set params = %*:"=""
|
||||
echo UAC.ShellExecute "cmd.exe", "/c %~s0 %params%", "", "runas", 1 >> "%temp%\getadmin.vbs"
|
||||
|
||||
"%temp%\getadmin.vbs"
|
||||
del "%temp%\getadmin.vbs"
|
||||
exit /B
|
||||
|
||||
:gotAdmin
|
||||
pushd "%CD%"
|
||||
CD /D "%~dp0"
|
||||
regsvr32 "UnityCaptureFilter32.dll"
|
||||
regsvr32 "UnityCaptureFilter64.dll"
|
||||
:--------------------------------------
|
||||
1
pc/virtualcam/LICENSE-UnityCapture.txt
Normal file
1
pc/virtualcam/LICENSE-UnityCapture.txt
Normal file
@@ -0,0 +1 @@
|
||||
404: Not Found
|
||||
28
pc/virtualcam/Uninstall.bat
Normal file
28
pc/virtualcam/Uninstall.bat
Normal file
@@ -0,0 +1,28 @@
|
||||
@echo off
|
||||
|
||||
:: BatchGotAdmin
|
||||
:-------------------------------------
|
||||
REM --> Check for permissions
|
||||
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
|
||||
|
||||
REM --> If error flag set, we do not have admin.
|
||||
if '%errorlevel%' NEQ '0' (
|
||||
echo Requesting administrative privileges...
|
||||
goto UACPrompt
|
||||
) else ( goto gotAdmin )
|
||||
|
||||
:UACPrompt
|
||||
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
|
||||
set params = %*:"=""
|
||||
echo UAC.ShellExecute "cmd.exe", "/c %~s0 %params%", "", "runas", 1 >> "%temp%\getadmin.vbs"
|
||||
|
||||
"%temp%\getadmin.vbs"
|
||||
del "%temp%\getadmin.vbs"
|
||||
exit /B
|
||||
|
||||
:gotAdmin
|
||||
pushd "%CD%"
|
||||
CD /D "%~dp0"
|
||||
regsvr32 /u "UnityCaptureFilter32.dll"
|
||||
regsvr32 /u "UnityCaptureFilter64.dll"
|
||||
:--------------------------------------
|
||||
BIN
pc/virtualcam/UnityCaptureFilter32.dll
Normal file
BIN
pc/virtualcam/UnityCaptureFilter32.dll
Normal file
Binary file not shown.
BIN
pc/virtualcam/UnityCaptureFilter64.dll
Normal file
BIN
pc/virtualcam/UnityCaptureFilter64.dll
Normal file
Binary file not shown.
@@ -70,7 +70,7 @@ async function setLang(code) {
|
||||
S = r.strings;
|
||||
CFG.lang = r.lang;
|
||||
applyStrings();
|
||||
refreshObs(CFG.obs_installed);
|
||||
refreshVcam();
|
||||
setLangButton(CFG.langs, r.lang);
|
||||
buildLangMenu(CFG.langs, r.lang);
|
||||
pollUsb();
|
||||
@@ -80,6 +80,8 @@ async function setLang(code) {
|
||||
function setStatus(state, info) {
|
||||
lastState = state;
|
||||
lastInfo = info;
|
||||
const pc = document.querySelector(".preview-card");
|
||||
if (pc) pc.classList.toggle("live", state === "connected");
|
||||
const dot = $("dot");
|
||||
dot.className = "dot " + (state || "");
|
||||
let text;
|
||||
@@ -105,14 +107,14 @@ function setLog(text, cls) {
|
||||
l.className = "log " + (cls || "");
|
||||
}
|
||||
|
||||
function refreshObs(installed) {
|
||||
function refreshVcam() {
|
||||
const label = $("obsLabel");
|
||||
const btn = $("obsBtn");
|
||||
if (installed) {
|
||||
label.textContent = "✓ " + S.obs_ready;
|
||||
if (CFG.vcam_ready) {
|
||||
label.textContent = "✓ " + S.vcam_ready;
|
||||
btn.classList.remove("warn");
|
||||
} else {
|
||||
label.textContent = S.install_obs;
|
||||
label.textContent = S.install_vcam;
|
||||
btn.classList.add("warn");
|
||||
}
|
||||
}
|
||||
@@ -124,7 +126,7 @@ async function init() {
|
||||
applyStrings();
|
||||
$("versionLbl").textContent = "v" + boot.version;
|
||||
$("trayChk").checked = !!boot.tray;
|
||||
refreshObs(boot.obs_installed);
|
||||
refreshVcam();
|
||||
setRunning(boot.running);
|
||||
|
||||
setLangButton(boot.langs, boot.lang);
|
||||
@@ -140,7 +142,19 @@ async function init() {
|
||||
$("navDiscord").onclick = () => window.pywebview.api.open_url(CFG.discord);
|
||||
$("navInsta").onclick = () => window.pywebview.api.open_url(CFG.instagram);
|
||||
$("navDonate").onclick = () => window.pywebview.api.open_url(CFG.donate);
|
||||
$("obsBtn").onclick = () => window.pywebview.api.open_url(CFG.obs_url);
|
||||
$("obsBtn").onclick = async () => {
|
||||
if (CFG.vcam_ready) return;
|
||||
if (CFG.has_installer) {
|
||||
setLog(S.vcam_installing, "");
|
||||
const ok = await window.pywebview.api.install_virtualcam();
|
||||
if (ok) {
|
||||
CFG.vcam_ready = true;
|
||||
refreshVcam();
|
||||
}
|
||||
} else {
|
||||
window.pywebview.api.open_url(CFG.obs_url);
|
||||
}
|
||||
};
|
||||
$("trayChk").onchange = (e) =>
|
||||
window.pywebview.api.save_setting("tray", e.target.checked);
|
||||
|
||||
@@ -151,7 +165,7 @@ async function init() {
|
||||
} else {
|
||||
setRunning(true);
|
||||
setStatus("starting", null);
|
||||
window.pywebview.api.start($("wifiIp").value.trim());
|
||||
window.pywebview.api.start($("wifiIp").value.trim(), $("wifiKey").value.trim());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -175,10 +189,12 @@ async function init() {
|
||||
pollUsb();
|
||||
setInterval(pollUsb, 3000);
|
||||
|
||||
$("previewImg").src = "http://127.0.0.1:8475/preview";
|
||||
$("wifiIp").value = boot.wifi_ip || "";
|
||||
$("wifiKey").value = boot.wifi_key || "";
|
||||
setRunning(true);
|
||||
setStatus("starting", null);
|
||||
window.pywebview.api.start(boot.wifi_ip || "");
|
||||
window.pywebview.api.start(boot.wifi_ip || "", boot.wifi_key || "");
|
||||
}
|
||||
|
||||
window.flexStatus = setStatus;
|
||||
|
||||
@@ -78,6 +78,11 @@
|
||||
<main class="main">
|
||||
<p class="tagline" id="tagline">Turn your phone into a webcam</p>
|
||||
|
||||
<div class="preview-card">
|
||||
<img id="previewImg" class="preview-img" alt="">
|
||||
<span class="preview-dot"></span>
|
||||
</div>
|
||||
|
||||
<div class="status-card">
|
||||
<span class="status-caption" id="statusCaption">Connection</span>
|
||||
<div class="status-main">
|
||||
@@ -93,7 +98,10 @@
|
||||
|
||||
<div class="field">
|
||||
<label id="wifiLabel">WiFi IP (optional)</label>
|
||||
<input type="text" id="wifiIp" placeholder="192.168.1.x">
|
||||
<div class="ip-row">
|
||||
<input type="text" id="wifiIp" placeholder="192.168.1.x">
|
||||
<input type="text" id="wifiKey" placeholder="Kod" maxlength="8">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="primary" id="toggleBtn">Start</button>
|
||||
|
||||
@@ -28,16 +28,17 @@ body {
|
||||
.app { display: flex; height: 100vh; }
|
||||
|
||||
.sidebar {
|
||||
width: 230px;
|
||||
width: 252px;
|
||||
background: var(--sidebar);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 18px 14px;
|
||||
transition: width 0.22s ease;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar.collapsed { width: 68px; }
|
||||
.sidebar.collapsed { width: 72px; }
|
||||
|
||||
.side-top { display: flex; align-items: center; gap: 10px; margin-bottom: 26px; }
|
||||
|
||||
@@ -63,19 +64,23 @@ body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--muted);
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
font-size: 13.5px;
|
||||
padding: 11px 12px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.nav-item:hover { background: var(--card); color: var(--fg); }
|
||||
.nav-item svg, .side-btn svg { flex-shrink: 0; }
|
||||
.nav-item .label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.side-btn .label { flex: 1; min-width: 0; line-height: 1.25; }
|
||||
|
||||
.side-actions {
|
||||
margin-top: 22px;
|
||||
@@ -217,6 +222,30 @@ body {
|
||||
|
||||
.tagline { color: var(--muted); font-size: 15px; }
|
||||
|
||||
.preview-card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
aspect-ratio: 16 / 10;
|
||||
background: #0c0e15;
|
||||
border: 1px solid #2a2f40;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.preview-img { width: 100%; height: 100%; object-fit: contain; display: block; }
|
||||
.preview-dot {
|
||||
position: absolute;
|
||||
top: 10px; right: 10px;
|
||||
width: 9px; height: 9px; border-radius: 50%;
|
||||
background: var(--gray);
|
||||
box-shadow: 0 0 0 4px rgba(107,114,128,.15);
|
||||
transition: background .2s, box-shadow .2s;
|
||||
}
|
||||
.preview-card.live .preview-dot {
|
||||
background: var(--green);
|
||||
box-shadow: 0 0 0 4px rgba(34,197,94,.18);
|
||||
}
|
||||
|
||||
.status-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -273,6 +302,9 @@ body {
|
||||
|
||||
.field { width: 100%; max-width: 360px; }
|
||||
.field label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
|
||||
.ip-row { display: flex; gap: 8px; }
|
||||
.ip-row #wifiIp { flex: 1; }
|
||||
.ip-row #wifiKey { width: 96px; text-align: center; letter-spacing: 1px; }
|
||||
.field input {
|
||||
width: 100%;
|
||||
background: var(--card);
|
||||
|
||||
26
pc/webgui.py
26
pc/webgui.py
@@ -11,6 +11,7 @@ import sysutil
|
||||
import config
|
||||
import i18n
|
||||
import presence
|
||||
import preview_server
|
||||
|
||||
|
||||
def base_dir():
|
||||
@@ -57,9 +58,12 @@ class Api:
|
||||
"obs_url": config.OBS_DOWNLOAD_URL,
|
||||
"version": config.APP_VERSION,
|
||||
"obs_installed": sysutil.obs_installed(),
|
||||
"vcam_ready": sysutil.obs_installed() or self.settings.get("vcam_installed", False),
|
||||
"has_installer": sysutil.virtualcam_installer() is not None,
|
||||
"tray": self.settings.get("tray", True),
|
||||
"running": self.engine.is_running(),
|
||||
"wifi_ip": self.settings.get("wifi_ip", ""),
|
||||
"wifi_key": self.settings.get("wifi_key", ""),
|
||||
}
|
||||
|
||||
def set_lang(self, lang):
|
||||
@@ -68,12 +72,14 @@ class Api:
|
||||
sysutil.save_settings(self.settings)
|
||||
return {"strings": self._strings(), "lang": self.t.lang}
|
||||
|
||||
def start(self, ip):
|
||||
def start(self, ip, key=""):
|
||||
ip = (ip or "").strip()
|
||||
if ip:
|
||||
self.settings["wifi_ip"] = ip
|
||||
sysutil.save_settings(self.settings)
|
||||
self.engine.set_wifi_ip(ip or self.settings.get("wifi_ip") or None)
|
||||
key = (key or "").strip()
|
||||
self.settings["wifi_ip"] = ip
|
||||
self.settings["wifi_key"] = key
|
||||
sysutil.save_settings(self.settings)
|
||||
self.engine.set_wifi_ip(ip or None)
|
||||
self.engine.set_key(key)
|
||||
self.engine.start()
|
||||
|
||||
def stop(self):
|
||||
@@ -86,6 +92,13 @@ class Api:
|
||||
def open_url(self, url):
|
||||
sysutil.open_url(url)
|
||||
|
||||
def install_virtualcam(self):
|
||||
ok = sysutil.install_virtualcam()
|
||||
if ok:
|
||||
self.settings["vcam_installed"] = True
|
||||
sysutil.save_settings(self.settings)
|
||||
return ok
|
||||
|
||||
def usb_connected(self):
|
||||
return adb_tools.detect_device() is not None
|
||||
|
||||
@@ -170,12 +183,13 @@ def main():
|
||||
api = Api()
|
||||
window = webview.create_window(
|
||||
"FlexCam", url=web_index(), js_api=api,
|
||||
width=760, height=520, min_size=(680, 480),
|
||||
width=1120, height=760, min_size=(900, 640),
|
||||
background_color="#14161f",
|
||||
)
|
||||
_WINDOW = window
|
||||
tray = TrayHolder(api, window)
|
||||
window.events.closing += tray.on_closing
|
||||
preview_server.PreviewServer(lambda: api.engine.preview_jpeg()).start()
|
||||
presence.start()
|
||||
webview.start()
|
||||
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user