Compare commits
4 Commits
ffmpegAudi
...
linuxHDMI
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b3c6cbee4 | ||
|
|
243b9d7acb | ||
|
|
58a0cac7db | ||
|
|
504877a969 |
@@ -1,6 +1,13 @@
|
||||
.github/workflows/build-image.yml
|
||||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
README.md
|
||||
LICENSE
|
||||
.github
|
||||
**/__pycache__
|
||||
**/*.pyc
|
||||
.venv
|
||||
venv
|
||||
node_modules
|
||||
web-console/node_modules
|
||||
web-console/.next
|
||||
*.log
|
||||
.process_runner
|
||||
.cursor
|
||||
mobile
|
||||
|
||||
12
.gitignore
vendored
@@ -165,3 +165,15 @@ cython_debug/
|
||||
#.idea/
|
||||
|
||||
backup_config/
|
||||
|
||||
# 根目录 npm(concurrently 等)
|
||||
node_modules/
|
||||
|
||||
# Next 开发缓存(静态导出目录 web-console/out 可纳入版本库以便无 Node 环境直接部署)
|
||||
web-console/.next/
|
||||
|
||||
# 无 PM2 时的本地进程状态
|
||||
.process_runner/
|
||||
|
||||
# 本机端口/主机覆盖(从 config/runtime.env.example 复制)
|
||||
config/runtime.env
|
||||
|
||||
33
Dockerfile
@@ -1,19 +1,28 @@
|
||||
FROM python:3.11-slim
|
||||
# 生产镜像:ffmpeg + 多阶段构建 Next 静态页 + Python API(默认端口 8101,减少与同机 SRS/代理 冲突)
|
||||
# docker compose up --build
|
||||
FROM node:20-bookworm-slim AS console
|
||||
WORKDIR /src/web-console
|
||||
COPY web-console/package.json web-console/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY web-console/ ./
|
||||
RUN npm run build
|
||||
|
||||
FROM python:3.12-slim-bookworm
|
||||
WORKDIR /app
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
PORT=8101
|
||||
|
||||
COPY . /app
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y curl gnupg && \
|
||||
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
|
||||
apt-get install -y nodejs
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ffmpeg ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& ffmpeg -version
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y ffmpeg tzdata && \
|
||||
ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
||||
dpkg-reconfigure -f noninteractive tzdata
|
||||
COPY . .
|
||||
COPY --from=console /src/web-console/out ./web-console/out
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
EXPOSE 8101
|
||||
CMD ["sh", "-c", "exec python -m uvicorn web:app --host 0.0.0.0 --port ${PORT}"]
|
||||
|
||||
5
config/easytier_network.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"networkName": "lr_f976289ba590",
|
||||
"networkSecret": "5f86ed227e8cfcf7d8b70b287502b682aa8d3ca0b0c560ed",
|
||||
"peerUrls": []
|
||||
}
|
||||
10
config/google_oauth.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"redirect_uri": "http://127.0.0.1:8001/youtube/oauth/callback",
|
||||
"web": {
|
||||
"client_id": "305371487651-iuqafultpqp0rvitojr70560k1foet0n.apps.googleusercontent.com",
|
||||
"client_secret": "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",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"redirect_uris": ["http://127.0.0.1:8001/youtube/oauth/callback"]
|
||||
}
|
||||
}
|
||||
11
config/runtime.env.example
Normal file
@@ -0,0 +1,11 @@
|
||||
# 复制本文件为同目录下的 runtime.env 后生效(已加入 .gitignore,不会提交)
|
||||
# 启动器 scripts/launch.py 会自动加载 runtime.env 中的键(不覆盖已存在的环境变量)
|
||||
|
||||
# 局域网内任意浏览器访问控制台时,请保持 0.0.0.0
|
||||
HOST=0.0.0.0
|
||||
|
||||
# Web 控制台(FastAPI + 静态页)端口;与同机 SRS / Clash / Neko 等冲突时请改成未占用端口,例如 8101、9200
|
||||
PORT=8001
|
||||
|
||||
# 开发模式时 Next 端口(一般无需改)
|
||||
# NEXT_DEV_PORT=3000
|
||||
1
config/web2_api_token.txt
Normal file
@@ -0,0 +1 @@
|
||||
9d0103f49a3fea59311c014b9468e3ad2bd14d4f8334a7ec
|
||||
1
config/web2_bind_host.txt
Normal file
@@ -0,0 +1 @@
|
||||
0.0.0.0
|
||||
6
dev.bat
Normal file
@@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
cd /d "%~dp0"
|
||||
if not defined PORT set "PORT=8001"
|
||||
echo [dev] API+Next 开发模式 PORT=%PORT%
|
||||
echo [dev] 浏览器打开 http://localhost:3000
|
||||
python scripts\launch.py --dev --port %PORT%
|
||||
8
dev.sh
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# 开发:API(热重载) + Next dev;浏览器打开 http://localhost:3000
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
export PORT="${PORT:-8001}"
|
||||
PY=python3
|
||||
command -v python3 >/dev/null 2>&1 || PY=python
|
||||
exec "$PY" scripts/launch.py --dev --port "$PORT"
|
||||
10
docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
# 控制台默认 8101(与 SRS 8080/1985、Clash 7890 等错开);改端口请同步修改左侧宿主机映射
|
||||
services:
|
||||
live-console:
|
||||
build: .
|
||||
ports:
|
||||
- "${LIVE_CONSOLE_PORT:-8101}:8101"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
HOST: "0.0.0.0"
|
||||
PORT: "8101"
|
||||
26
ecosystem.config.cjs
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* PM2:在项目根执行 `pm2 start ecosystem.config.cjs` 后 `pm2 save`
|
||||
* web 使用 scripts/launch.py:若缺少 web-console/out 会自动 npm build 再起 uvicorn
|
||||
*/
|
||||
const path = require("path");
|
||||
const root = __dirname;
|
||||
const py = process.env.PYTHON || "python3";
|
||||
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "web",
|
||||
cwd: root,
|
||||
script: path.join(root, "scripts", "launch.py"),
|
||||
interpreter: py,
|
||||
env: {
|
||||
HOST: "0.0.0.0",
|
||||
PORT: process.env.PORT || "8001",
|
||||
},
|
||||
},
|
||||
// { name: "youtube", cwd: root, script: py, args: "youtube.py" },
|
||||
// { name: "tiktok", cwd: root, script: py, args: "tiktok.py" },
|
||||
// Linux: { name: "obs", cwd: root, script: "bash", args: `-lc ${JSON.stringify(path.join(root, "obs.sh"))}` },
|
||||
// Win: { name: "obs", cwd: root, script: "cmd.exe", args: "/c obs.bat" },
|
||||
],
|
||||
};
|
||||
10
index.html
@@ -84,7 +84,7 @@ button.loading::after{content:" ⏳";animation:spin 1s linear infinite}
|
||||
<pre id="pm2Log">正在加载状态与日志...</pre>
|
||||
</div>
|
||||
<script>
|
||||
/** 进程列表仅来自 GET /process_monitor(web2.py 启动时扫描项目根目录自动识别);PM2 名 = 脚本主文件名(无扩展名)。 */
|
||||
/** 进程列表来自 GET /process_monitor(web.py 扫描根目录);PM2 名 = 脚本主文件名(无扩展名)。 */
|
||||
let PROCESS_MONITOR = [];
|
||||
|
||||
function pm2NameFromScript(script) {
|
||||
@@ -94,7 +94,9 @@ function pm2NameFromScript(script) {
|
||||
}
|
||||
|
||||
function isYoutubeScript(script) {
|
||||
return /\.py$/i.test(script) && pm2NameFromScript(script).startsWith('youtube');
|
||||
if (!/\.py$/i.test(script)) return false;
|
||||
var s = pm2NameFromScript(script).toLowerCase();
|
||||
return s.startsWith('youtube') || s.startsWith('douyin_youtube');
|
||||
}
|
||||
function isTiktokScript(script) {
|
||||
return /\.py$/i.test(script) && pm2NameFromScript(script).startsWith('tiktok');
|
||||
@@ -315,12 +317,12 @@ document.getElementById('pm2Buttons').addEventListener('click', e=>{
|
||||
try {
|
||||
await loadProcessMonitor();
|
||||
} catch (e) {
|
||||
logEl.textContent = '无法加载 /process_monitor(请用本服务打开的页面访问,或检查 web2 是否已启动)\n' + (e && e.message ? e.message : '');
|
||||
logEl.textContent = '无法加载 /process_monitor(请用本服务打开的页面访问,或检查 web.py / uvicorn 是否已启动)\n' + (e && e.message ? e.message : '');
|
||||
statusIndicator.textContent = '❌ 未连接';
|
||||
return;
|
||||
}
|
||||
if (!PROCESS_MONITOR.length) {
|
||||
logEl.textContent = '未发现任何入口脚本(项目根应有 tiktok*.py / youtube*.py / obs*.sh,且 Web 为当前 web*.py)';
|
||||
logEl.textContent = '未发现任何入口脚本(项目根应有 tiktok*.py / youtube*.py / douyin_youtube*.py / obs 脚本,且含 web.py)';
|
||||
return;
|
||||
}
|
||||
fillProcessSelect();
|
||||
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 73 KiB |
16
mobile/android/.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
build/
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
.cxx/
|
||||
|
||||
# Bundle artifacts
|
||||
*.jsbundle
|
||||
182
mobile/android/app/build.gradle
Normal file
@@ -0,0 +1,182 @@
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "org.jetbrains.kotlin.android"
|
||||
apply plugin: "com.facebook.react"
|
||||
|
||||
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
|
||||
|
||||
/**
|
||||
* This is the configuration block to customize your React Native Android app.
|
||||
* By default you don't need to apply any configuration, just uncomment the lines you need.
|
||||
*/
|
||||
react {
|
||||
entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
|
||||
reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
|
||||
hermesCommand = new File(["node", "--print", "require.resolve('hermes-compiler/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/hermesc/%OS-BIN%/hermesc"
|
||||
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
|
||||
|
||||
enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean()
|
||||
// Use Expo CLI to bundle the app, this ensures the Metro config
|
||||
// works correctly with Expo projects.
|
||||
cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
|
||||
bundleCommand = "export:embed"
|
||||
|
||||
/* Folders */
|
||||
// The root of your project, i.e. where "package.json" lives. Default is '../..'
|
||||
// root = file("../../")
|
||||
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
|
||||
// reactNativeDir = file("../../node_modules/react-native")
|
||||
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
|
||||
// codegenDir = file("../../node_modules/@react-native/codegen")
|
||||
|
||||
/* Variants */
|
||||
// The list of variants to that are debuggable. For those we're going to
|
||||
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
|
||||
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
|
||||
// debuggableVariants = ["liteDebug", "prodDebug"]
|
||||
|
||||
/* Bundling */
|
||||
// A list containing the node command and its flags. Default is just 'node'.
|
||||
// nodeExecutableAndArgs = ["node"]
|
||||
|
||||
//
|
||||
// The path to the CLI configuration file. Default is empty.
|
||||
// bundleConfig = file(../rn-cli.config.js)
|
||||
//
|
||||
// The name of the generated asset file containing your JS bundle
|
||||
// bundleAssetName = "MyApplication.android.bundle"
|
||||
//
|
||||
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
|
||||
// entryFile = file("../js/MyApplication.android.js")
|
||||
//
|
||||
// A list of extra flags to pass to the 'bundle' commands.
|
||||
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
|
||||
// extraPackagerArgs = []
|
||||
|
||||
/* Hermes Commands */
|
||||
// The hermes compiler command to run. By default it is 'hermesc'
|
||||
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
|
||||
//
|
||||
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
|
||||
// hermesFlags = ["-O", "-output-source-map"]
|
||||
|
||||
/* Autolinking */
|
||||
autolinkLibrariesWithApp()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization).
|
||||
*/
|
||||
def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore (JSC)
|
||||
*
|
||||
* For example, to use the international variant, you can use:
|
||||
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
|
||||
*
|
||||
* The international variant includes ICU i18n library and necessary data
|
||||
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
|
||||
* give correct results when using with locales other than en-US. Note that
|
||||
* this variant is about 6MiB larger per architecture than default.
|
||||
*/
|
||||
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
compileSdk rootProject.ext.compileSdkVersion
|
||||
|
||||
namespace 'com.douyin.recorder.remote'
|
||||
defaultConfig {
|
||||
applicationId 'com.douyin.recorder.remote'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('debug.keystore')
|
||||
storePassword 'android'
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
// Caution! In production, you need to generate your own keystore file.
|
||||
// see https://reactnative.dev/docs/signed-apk-android.
|
||||
signingConfig signingConfigs.debug
|
||||
def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
|
||||
shrinkResources enableShrinkResources.toBoolean()
|
||||
minifyEnabled enableMinifyInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
|
||||
crunchPngs enablePngCrunchInRelease.toBoolean()
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
|
||||
useLegacyPackaging enableLegacyPackaging.toBoolean()
|
||||
}
|
||||
}
|
||||
androidResources {
|
||||
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
|
||||
// Apply static values from `gradle.properties` to the `android.packagingOptions`
|
||||
// Accepts values in comma delimited lists, example:
|
||||
// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini
|
||||
["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop ->
|
||||
// Split option: 'foo,bar' -> ['foo', 'bar']
|
||||
def options = (findProperty("android.packagingOptions.$prop") ?: "").split(",");
|
||||
// Trim all elements in place.
|
||||
for (i in 0..<options.size()) options[i] = options[i].trim();
|
||||
// `[] - ""` is essentially `[""].filter(Boolean)` removing all empty strings.
|
||||
options -= ""
|
||||
|
||||
if (options.length > 0) {
|
||||
println "android.packagingOptions.$prop += $options ($options.length)"
|
||||
// Ex: android.packagingOptions.pickFirsts += '**/SCCS/**'
|
||||
options.each {
|
||||
android.packagingOptions[prop] += it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// The version of react-native is set by the React Native Gradle Plugin
|
||||
implementation("com.facebook.react:react-android")
|
||||
|
||||
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
|
||||
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
|
||||
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
|
||||
|
||||
if (isGifEnabled) {
|
||||
// For animated gif support
|
||||
implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}")
|
||||
}
|
||||
|
||||
if (isWebpEnabled) {
|
||||
// For webp support
|
||||
implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}")
|
||||
if (isWebpAnimatedEnabled) {
|
||||
// Animated webp support
|
||||
implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}")
|
||||
}
|
||||
}
|
||||
|
||||
if (hermesEnabled.toBoolean()) {
|
||||
implementation("com.facebook.react:hermes-android")
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
BIN
mobile/android/app/debug.keystore
Normal file
14
mobile/android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# react-native-reanimated
|
||||
-keep class com.swmansion.reanimated.** { *; }
|
||||
-keep class com.facebook.react.turbomodule.** { *; }
|
||||
|
||||
# Add any project specific keep options here:
|
||||
7
mobile/android/app/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
|
||||
</manifest>
|
||||
33
mobile/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" tools:replace="android:maxSdkVersion"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" tools:replace="android:maxSdkVersion"/>
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false">
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|smallestScreenSize" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="mobile"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.douyin.recorder.remote
|
||||
import expo.modules.splashscreen.SplashScreenManager
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
||||
import com.facebook.react.ReactActivity
|
||||
import com.facebook.react.ReactActivityDelegate
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
||||
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
||||
|
||||
import expo.modules.ReactActivityDelegateWrapper
|
||||
|
||||
class MainActivity : ReactActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// Set the theme to AppTheme BEFORE onCreate to support
|
||||
// coloring the background, status bar, and navigation bar.
|
||||
// This is required for expo-splash-screen.
|
||||
// setTheme(R.style.AppTheme);
|
||||
// @generated begin expo-splashscreen - expo prebuild (DO NOT MODIFY) sync-f3ff59a738c56c9a6119210cb55f0b613eb8b6af
|
||||
SplashScreenManager.registerOnActivity(this)
|
||||
// @generated end expo-splashscreen
|
||||
super.onCreate(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
||||
* rendering of the component.
|
||||
*/
|
||||
override fun getMainComponentName(): String = "main"
|
||||
|
||||
/**
|
||||
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
|
||||
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
|
||||
*/
|
||||
override fun createReactActivityDelegate(): ReactActivityDelegate {
|
||||
return ReactActivityDelegateWrapper(
|
||||
this,
|
||||
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
|
||||
object : DefaultReactActivityDelegate(
|
||||
this,
|
||||
mainComponentName,
|
||||
fabricEnabled
|
||||
){})
|
||||
}
|
||||
|
||||
/**
|
||||
* Align the back button behavior with Android S
|
||||
* where moving root activities to background instead of finishing activities.
|
||||
* @see <a href="https://developer.android.com/reference/android/app/Activity#onBackPressed()">onBackPressed</a>
|
||||
*/
|
||||
override fun invokeDefaultOnBackPressed() {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||
if (!moveTaskToBack(false)) {
|
||||
// For non-root activities, use the default implementation to finish them.
|
||||
super.invokeDefaultOnBackPressed()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Use the default back button implementation on Android S
|
||||
// because it's doing more than [Activity.moveTaskToBack] in fact.
|
||||
super.invokeDefaultOnBackPressed()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.douyin.recorder.remote
|
||||
|
||||
import android.app.Application
|
||||
import android.content.res.Configuration
|
||||
|
||||
import com.facebook.react.PackageList
|
||||
import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.ReactHost
|
||||
import com.facebook.react.common.ReleaseLevel
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
|
||||
|
||||
import expo.modules.ApplicationLifecycleDispatcher
|
||||
import expo.modules.ExpoReactHostFactory
|
||||
|
||||
class MainApplication : Application(), ReactApplication {
|
||||
|
||||
override val reactHost: ReactHost by lazy {
|
||||
ExpoReactHostFactory.getDefaultReactHost(
|
||||
context = applicationContext,
|
||||
packageList =
|
||||
PackageList(this).packages.apply {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// add(MyReactNativePackage())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
DefaultNewArchitectureEntryPoint.releaseLevel = try {
|
||||
ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
ReleaseLevel.STABLE
|
||||
}
|
||||
loadReactNative(this)
|
||||
ApplicationLifecycleDispatcher.onApplicationCreate(this)
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 65 KiB |
@@ -0,0 +1,6 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/splashscreen_background"/>
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splashscreen_logo"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
|
||||
android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
|
||||
android:insetTop="@dimen/abc_edit_text_inset_top_material"
|
||||
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"
|
||||
>
|
||||
|
||||
<selector>
|
||||
<!--
|
||||
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
|
||||
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
|
||||
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
|
||||
|
||||
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
||||
|
||||
For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.
|
||||
-->
|
||||
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
||||
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
|
||||
</selector>
|
||||
|
||||
</inset>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
BIN
mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
BIN
mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
BIN
mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
BIN
mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
BIN
mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 20 KiB |
1
mobile/android/app/src/main/res/values-night/colors.xml
Normal file
@@ -0,0 +1 @@
|
||||
<resources/>
|
||||
5
mobile/android/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<color name="splashscreen_background">#ffffff</color>
|
||||
<color name="iconBackground">#E6F4FE</color>
|
||||
<color name="colorPrimary">#023c69</color>
|
||||
</resources>
|
||||
4
mobile/android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">直播录制助手</string>
|
||||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
</resources>
|
||||
14
mobile/android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="Theme.App.SplashScreen" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/splashscreen_background</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splashscreen_logo</item>
|
||||
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||
<item name="android:windowSplashScreenBehavior">icon_preferred</item>
|
||||
</style>
|
||||
</resources>
|
||||