mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-15 08:43:44 +08:00
fix(player): allow managed media proxy opt-in
This commit is contained in:
@@ -21,5 +21,14 @@ REDIS_URL=redis://redis:6379/0
|
||||
UPSTASH_REDIS_REST_URL=
|
||||
UPSTASH_REDIS_REST_TOKEN=
|
||||
|
||||
# Media proxy runtime policy:
|
||||
# - auto: self-hosted/Docker enables the proxy; Vercel/Cloudflare disables it.
|
||||
# - enabled: allow the player to use proxy retry on managed deployments, useful
|
||||
# for iOS/Safari HLS sources that fail direct playback.
|
||||
# - disabled: force direct playback everywhere.
|
||||
KVIDEO_MEDIA_PROXY_MODE=auto
|
||||
|
||||
# If no authentication is configured, /api/proxy still remains private unless
|
||||
# this is set to true. Authenticated deployments do not need public relay.
|
||||
KVIDEO_PUBLIC_RELAY_ENABLED=false
|
||||
KVIDEO_OUTBOUND_PRIVATE_HOST_ALLOWLIST=
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 4.9.5 - 2026-05-02
|
||||
|
||||
- 新增 `KVIDEO_MEDIA_PROXY_MODE` 运行时开关:Vercel / Cloudflare 默认仍保持受限托管模式,但部署者可显式设置 `enabled`,让播放器的“智能重试 / 总是代理”策略在 iOS/Safari HLS 直连失败时生效。
|
||||
- 播放器设置里的代理选项调整为“多环境播放策略”,文案明确覆盖桌面、移动端、iOS Safari 与托管部署差异。
|
||||
- Docker Compose、`.env.example`、`wrangler.toml` 和 README 补齐媒体代理、公共中继、Upstash Redis 与托管环境边界说明。
|
||||
- 新增运行时能力回归测试,覆盖托管环境默认禁用代理、显式启用代理,以及自托管强制直连的行为。
|
||||
- Redis 客户端测试补充 Vercel 缺少 Upstash Token 时的降级断言,避免 `/api/user/sync` 因不完整 Redis 配置再次返回 500。
|
||||
|
||||
## 4.9.4 - 2026-05-02
|
||||
|
||||
- Docker Compose 新增持久化 Redis 服务,默认通过 `REDIS_URL=redis://redis:6379/0` 启用自托管托管账户存储。
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
| Android TV 壳应用 | 支持 | 仓库内提供 `android-tv/` 工程 |
|
||||
| 自托管 Node.js | 支持 | 完整能力路径 |
|
||||
| Docker | 支持 | 完整能力路径,推荐部署方式之一 |
|
||||
| Cloudflare Workers via OpenNext | 支持 | Cloudflare 主支持路径,但运行时会进入受限托管模式 |
|
||||
| Vercel / 托管 Cloudflare 部署 | 支持但受限 | 自动关闭外部媒体代理、热链转发和 IPTV 流中继 |
|
||||
| Cloudflare Workers via OpenNext | 支持 | Cloudflare 主支持路径;默认受限,但可显式开启媒体代理 |
|
||||
| Vercel / 托管 Cloudflare 部署 | 支持但受限 | 默认关闭媒体代理、热链转发和 IPTV;可用 `KVIDEO_MEDIA_PROXY_MODE=enabled` 修复部分 iOS/Safari HLS 兼容问题 |
|
||||
| Apple TV / tvOS 原生打包 | 不支持 | 旧的 WebView 壳路径已移除,不再宣称支持 |
|
||||
|
||||
Apple TV 用户当前应使用浏览器、AirPlay,或其他已经被支持的投屏路径,而不是依赖仓库内不存在的完整 tvOS 产品链路。
|
||||
@@ -70,7 +70,7 @@ Apple TV 用户当前应使用浏览器、AirPlay,或其他已经被支持的
|
||||
- **移动端优化**:移动端播放器布局、双击手势、触摸交互单独处理
|
||||
- **Google Cast / Chromecast**:播放器页面加载 Cast SDK
|
||||
- **画中画(PiP)**:桌面浏览器与 Android WebView 都有对应兼容逻辑
|
||||
- **代理播放模式**:直连、仅代理、智能重试等模式可切换
|
||||
- **多环境播放策略**:直连、仅代理、智能重试等模式可切换,托管环境可用部署变量开启 iOS/Safari 兼容代理
|
||||
- **实际分辨率探测**:会对当前播放源和线路列表中的源做分辨率探测与缓存
|
||||
- **广告过滤回退**:广告过滤失败时会退回原始视频流,而不是直接把播放器打死
|
||||
- **键盘快捷键**:空格/K、F、M、P、方向键、J/L 等常用控制都已接入
|
||||
@@ -106,7 +106,7 @@ Apple TV 用户当前应使用浏览器、AirPlay,或其他已经被支持的
|
||||
- **播放器内搜索优化**:大列表搜索使用 `useTransition` 降低阻塞
|
||||
|
||||
> [!WARNING]
|
||||
> IPTV 与媒体代理在 Vercel / Cloudflare 等托管部署中会被自动关闭。需要完整 IPTV 能力时,直接用 Docker 或传统 Node.js 自托管,别自欺欺人地把受限托管平台当完整功能环境。
|
||||
> IPTV 在 Vercel / Cloudflare 等托管部署中会被自动关闭。媒体代理默认关闭,但可以通过 `KVIDEO_MEDIA_PROXY_MODE=enabled` 显式开启,用于 iOS/Safari HLS 直连失败后的代理重试。需要完整 IPTV、中继和自定义转发能力时,直接用 Docker 或传统 Node.js 自托管。
|
||||
|
||||
### 豆瓣、推荐与元数据
|
||||
|
||||
@@ -169,7 +169,7 @@ Apple TV 用户当前应使用浏览器、AirPlay,或其他已经被支持的
|
||||
- 代理路由不会转发 Cookie,也不会伪造客户端 IP / `Origin` / `Referer`
|
||||
- 只要开启认证,`AUTH_SECRET` 就是硬要求
|
||||
- 登录失败会触发节流,接口可能返回 `429` 与 `Retry-After`
|
||||
- Vercel / Cloudflare 运行时会进入受限托管模式,自动关闭外部媒体代理、热链转发和 IPTV 流中继
|
||||
- Vercel / Cloudflare 运行时会进入受限托管模式,默认关闭外部媒体代理、热链转发和 IPTV 流中继;媒体代理可通过 `KVIDEO_MEDIA_PROXY_MODE=enabled` 单独开启
|
||||
|
||||
## 认证模式与权限
|
||||
|
||||
@@ -249,6 +249,8 @@ Apple TV 用户当前应使用浏览器、AirPlay,或其他已经被支持的
|
||||
|
||||
| 变量名 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `KVIDEO_MEDIA_PROXY_MODE` | `auto` | 媒体代理运行策略:`auto` 表示自托管 / Docker 开启、Vercel / Cloudflare 默认关闭;`enabled` 表示托管环境也允许播放器代理重试;`disabled` 表示所有环境强制直连 |
|
||||
| `KVIDEO_MEDIA_PROXY_ENABLED` | - | 兼容布尔别名;`true` 等同 `KVIDEO_MEDIA_PROXY_MODE=enabled`,`false` 等同 `disabled` |
|
||||
| `KVIDEO_PUBLIC_RELAY_ENABLED` | `false` | 是否允许未认证场景下的公共代理访问 |
|
||||
| `KVIDEO_OUTBOUND_PRIVATE_HOST_ALLOWLIST` | - | 允许显式访问的私网 / 内网主机白名单,逗号分隔 |
|
||||
|
||||
@@ -392,7 +394,9 @@ docker run -d \
|
||||
- 关闭热链转发
|
||||
- 关闭 IPTV 流中继
|
||||
|
||||
如果你需要 `/api/proxy`、IPTV 中继、自定义 `User-Agent` / `Referer` 转发等完整能力,就不要继续拿受限托管环境硬扛,直接换 Docker 或传统 Node.js 自托管。
|
||||
如果你只遇到 iOS/Safari 播放 `.m3u8` 失败,而桌面和 Android 正常,可以在 Vercel / Cloudflare 设置 `KVIDEO_MEDIA_PROXY_MODE=enabled`,然后在播放器设置里保持“智能重试”或切到“总是代理”。未启用登录认证时,还必须显式设置 `KVIDEO_PUBLIC_RELAY_ENABLED=true`,否则 `/api/proxy` 会继续拒绝公共访问。
|
||||
|
||||
这不会把托管环境变成完整能力路径:IPTV 中继、自定义 `User-Agent` / `Referer` 透传和更复杂的热链转发仍应使用 Docker 或传统 Node.js 自托管。
|
||||
|
||||
### Cloudflare 路径
|
||||
|
||||
@@ -405,7 +409,7 @@ npm run cf:preview
|
||||
|
||||
`npm run pages:build` 仍然保留,但只是给现存兼容链路兜底的遗留构建脚本,不是这条分支主张的 Cloudflare 主路径。
|
||||
|
||||
Cloudflare Workers 不能使用 Docker Compose 里的 TCP Redis。需要托管账户与同步时,在 Cloudflare 侧配置 `AUTH_SECRET`、`ADMIN_PASSWORD` / `ACCOUNTS`,以及 `UPSTASH_REDIS_REST_URL` + `UPSTASH_REDIS_REST_TOKEN`。
|
||||
Cloudflare Workers 不能使用 Docker Compose 里的 TCP Redis。需要托管账户与同步时,在 Cloudflare 侧配置 `AUTH_SECRET`、`ADMIN_PASSWORD` / `ACCOUNTS`,以及 `UPSTASH_REDIS_REST_URL` + `UPSTASH_REDIS_REST_TOKEN`。如果只需要修复 Safari/HLS 直连兼容问题,可以额外设置 `KVIDEO_MEDIA_PROXY_MODE=enabled`;公共无登录部署还需要 `KVIDEO_PUBLIC_RELAY_ENABLED=true`。
|
||||
|
||||
### Android TV APK
|
||||
|
||||
|
||||
+13
-1
@@ -4,8 +4,20 @@
|
||||
"name": "KVideo",
|
||||
"branch": "main"
|
||||
},
|
||||
"currentVersion": "4.9.4",
|
||||
"currentVersion": "4.9.5",
|
||||
"releases": [
|
||||
{
|
||||
"version": "4.9.5",
|
||||
"publishedAt": "2026-05-02",
|
||||
"title": "托管环境 Safari 播放兼容开关",
|
||||
"notes": [
|
||||
"新增 KVIDEO_MEDIA_PROXY_MODE 运行时开关:Vercel / Cloudflare 默认仍保持受限托管模式,但部署者可显式设置 enabled,让播放器的“智能重试 / 总是代理”策略在 iOS/Safari HLS 直连失败时生效。",
|
||||
"播放器设置里的代理选项调整为“多环境播放策略”,文案明确覆盖桌面、移动端、iOS Safari 与托管部署差异。",
|
||||
"Docker Compose、.env.example、wrangler.toml 和 README 补齐媒体代理、公共中继、Upstash Redis 与托管环境边界说明。",
|
||||
"新增运行时能力回归测试,覆盖托管环境默认禁用代理、显式启用代理,以及自托管强制直连的行为。",
|
||||
"Redis 客户端测试补充 Vercel 缺少 Upstash Token 时的降级断言,避免 /api/user/sync 因不完整 Redis 配置再次返回 500。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "4.9.4",
|
||||
"publishedAt": "2026-05-02",
|
||||
|
||||
@@ -164,10 +164,10 @@ export function PlayerSettings({
|
||||
<div>
|
||||
<h3 className="font-medium text-[var(--text-color)] mb-2 inline-flex items-center gap-2">
|
||||
<Icons.Globe size={18} className="text-[var(--accent-color)]" />
|
||||
代理播放模式
|
||||
多环境播放策略
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-color-secondary)] mb-4">
|
||||
控制视频播放时的网络请求策略
|
||||
控制不同浏览器、iOS Safari 和托管部署下的视频请求策略
|
||||
</p>
|
||||
{mediaProxyEnabled ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
@@ -179,7 +179,7 @@ export function PlayerSettings({
|
||||
}`}
|
||||
>
|
||||
<div className="font-semibold">智能重试 (推荐)</div>
|
||||
<div className="text-sm opacity-80 mt-1">直连优先,失败时尝试代理</div>
|
||||
<div className="text-sm opacity-80 mt-1">直连优先,失败时尝试代理,适合 iOS/Safari 兼容</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onProxyModeChange('none')}
|
||||
@@ -189,7 +189,7 @@ export function PlayerSettings({
|
||||
}`}
|
||||
>
|
||||
<div className="font-semibold">仅直连</div>
|
||||
<div className="text-sm opacity-80 mt-1">不使用代理,失败则报错</div>
|
||||
<div className="text-sm opacity-80 mt-1">不使用代理,适合源站本身跨端兼容时</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onProxyModeChange('always')}
|
||||
@@ -199,7 +199,7 @@ export function PlayerSettings({
|
||||
}`}
|
||||
>
|
||||
<div className="font-semibold">总是代理</div>
|
||||
<div className="text-sm opacity-80 mt-1">所有请求都通过代理转发</div>
|
||||
<div className="text-sm opacity-80 mt-1">所有播放请求都通过服务端代理转发</div>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -22,6 +22,9 @@ services:
|
||||
REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
|
||||
UPSTASH_REDIS_REST_URL: ${UPSTASH_REDIS_REST_URL:-}
|
||||
UPSTASH_REDIS_REST_TOKEN: ${UPSTASH_REDIS_REST_TOKEN:-}
|
||||
KVIDEO_MEDIA_PROXY_MODE: ${KVIDEO_MEDIA_PROXY_MODE:-auto}
|
||||
KVIDEO_PUBLIC_RELAY_ENABLED: ${KVIDEO_PUBLIC_RELAY_ENABLED:-false}
|
||||
KVIDEO_OUTBOUND_PRIVATE_HOST_ALLOWLIST: ${KVIDEO_OUTBOUND_PRIVATE_HOST_ALLOWLIST:-}
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -16,21 +16,50 @@ function isCloudflareDeployment(): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeToggle(value: string | undefined): boolean | null {
|
||||
const normalized = value?.trim().toLowerCase();
|
||||
if (!normalized) return null;
|
||||
|
||||
if (['1', 'true', 'yes', 'on', 'enabled', 'enable'].includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (['0', 'false', 'no', 'off', 'disabled', 'disable'].includes(normalized)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getMediaProxyOverride(): boolean | null {
|
||||
const explicitMode = normalizeToggle(process.env.KVIDEO_MEDIA_PROXY_MODE);
|
||||
if (explicitMode !== null) return explicitMode;
|
||||
|
||||
return normalizeToggle(process.env.KVIDEO_MEDIA_PROXY_ENABLED);
|
||||
}
|
||||
|
||||
function getRestrictedFeatures(
|
||||
deploymentProvider: RuntimeFeatures['deploymentProvider'],
|
||||
deploymentProviderLabel: string
|
||||
): RuntimeFeatures {
|
||||
const mediaProxyEnabled = getMediaProxyOverride() === true;
|
||||
const restrictionSummary = mediaProxyEnabled
|
||||
? `${deploymentProviderLabel} 托管部署已通过部署变量显式开启外部媒体代理;IPTV 流中继仍关闭。未启用认证时,公共代理仍需要 KVIDEO_PUBLIC_RELAY_ENABLED=true。`
|
||||
: `${deploymentProviderLabel} 托管部署默认关闭外部媒体代理、热链转发和 IPTV 流中继。若只需要修复 iOS/Safari HLS 播放兼容,可设置 KVIDEO_MEDIA_PROXY_MODE=enabled,并配置认证或 KVIDEO_PUBLIC_RELAY_ENABLED=true。`;
|
||||
|
||||
return {
|
||||
deploymentProvider,
|
||||
deploymentProviderLabel,
|
||||
restrictedManagedDeployment: true,
|
||||
mediaProxyEnabled: false,
|
||||
mediaProxyEnabled,
|
||||
iptvEnabled: false,
|
||||
restrictionSummary: `${deploymentProviderLabel} 托管部署会启用合规模式:关闭外部媒体代理、热链转发和 IPTV 流中继。需要这些能力时请改用 Docker 或传统 Node.js 自托管。`,
|
||||
restrictionSummary,
|
||||
};
|
||||
}
|
||||
|
||||
export function getRuntimeFeatures(): RuntimeFeatures {
|
||||
const mediaProxyOverride = getMediaProxyOverride();
|
||||
|
||||
if (isVercelDeployment()) {
|
||||
return getRestrictedFeatures('vercel', 'Vercel');
|
||||
}
|
||||
@@ -43,9 +72,10 @@ export function getRuntimeFeatures(): RuntimeFeatures {
|
||||
deploymentProvider: 'self-hosted',
|
||||
deploymentProviderLabel: '自托管',
|
||||
restrictedManagedDeployment: false,
|
||||
mediaProxyEnabled: true,
|
||||
mediaProxyEnabled: mediaProxyOverride !== false,
|
||||
iptvEnabled: true,
|
||||
restrictionSummary: null,
|
||||
restrictionSummary: mediaProxyOverride === false
|
||||
? '外部媒体代理已通过部署变量显式关闭。'
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "kvideo",
|
||||
"version": "4.9.4",
|
||||
"version": "4.9.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kvideo",
|
||||
"version": "4.9.4",
|
||||
"version": "4.9.5",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kvideo",
|
||||
"version": "4.9.4",
|
||||
"version": "4.9.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --port ${PORT:-3000}",
|
||||
|
||||
@@ -148,3 +148,20 @@ test('TCP Redis client supports Docker-style redis:// storage', async () => {
|
||||
delete process.env.REDIS_URL;
|
||||
resetRedisClientForTests();
|
||||
});
|
||||
|
||||
test('managed deployments ignore incomplete Upstash credentials without throwing', () => {
|
||||
process.env.VERCEL = '1';
|
||||
process.env.UPSTASH_REDIS_REST_URL = 'https://example.upstash.io';
|
||||
delete process.env.UPSTASH_REDIS_REST_TOKEN;
|
||||
process.env.REDIS_URL = 'redis://127.0.0.1:6379/0';
|
||||
resetRedisClientForTests();
|
||||
|
||||
try {
|
||||
assert.equal(getRedisClient(), null);
|
||||
} finally {
|
||||
delete process.env.VERCEL;
|
||||
delete process.env.UPSTASH_REDIS_REST_URL;
|
||||
delete process.env.REDIS_URL;
|
||||
resetRedisClientForTests();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { getRuntimeFeatures } from '@/lib/server/runtime-features';
|
||||
|
||||
const ENV_KEYS = [
|
||||
'VERCEL',
|
||||
'VERCEL_ENV',
|
||||
'CF_PAGES',
|
||||
'CF_PAGES_URL',
|
||||
'CLOUDFLARE_ACCOUNT_ID',
|
||||
'CF_ACCOUNT_ID',
|
||||
'WORKERS_CI',
|
||||
'KVIDEO_MEDIA_PROXY_MODE',
|
||||
'KVIDEO_MEDIA_PROXY_ENABLED',
|
||||
] as const;
|
||||
|
||||
function withRuntimeEnv(env: Record<string, string | undefined>, callback: () => void): void {
|
||||
const previous = new Map<string, string | undefined>();
|
||||
|
||||
for (const key of ENV_KEYS) {
|
||||
previous.set(key, process.env[key]);
|
||||
delete process.env[key];
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
callback();
|
||||
} finally {
|
||||
for (const key of ENV_KEYS) {
|
||||
const value = previous.get(key);
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('managed deployments disable media proxy by default', () => {
|
||||
withRuntimeEnv({ VERCEL: '1' }, () => {
|
||||
const features = getRuntimeFeatures();
|
||||
|
||||
assert.equal(features.deploymentProvider, 'vercel');
|
||||
assert.equal(features.restrictedManagedDeployment, true);
|
||||
assert.equal(features.mediaProxyEnabled, false);
|
||||
assert.equal(features.iptvEnabled, false);
|
||||
assert.match(features.restrictionSummary ?? '', /KVIDEO_MEDIA_PROXY_MODE=enabled/);
|
||||
});
|
||||
});
|
||||
|
||||
test('managed deployments can explicitly enable media proxy for Safari compatibility', () => {
|
||||
withRuntimeEnv({
|
||||
CF_PAGES: '1',
|
||||
KVIDEO_MEDIA_PROXY_MODE: 'enabled',
|
||||
}, () => {
|
||||
const features = getRuntimeFeatures();
|
||||
|
||||
assert.equal(features.deploymentProvider, 'cloudflare');
|
||||
assert.equal(features.restrictedManagedDeployment, true);
|
||||
assert.equal(features.mediaProxyEnabled, true);
|
||||
assert.equal(features.iptvEnabled, false);
|
||||
assert.match(features.restrictionSummary ?? '', /KVIDEO_PUBLIC_RELAY_ENABLED=true/);
|
||||
});
|
||||
});
|
||||
|
||||
test('self-hosted deployments can explicitly disable media proxy', () => {
|
||||
withRuntimeEnv({ KVIDEO_MEDIA_PROXY_MODE: 'disabled' }, () => {
|
||||
const features = getRuntimeFeatures();
|
||||
|
||||
assert.equal(features.deploymentProvider, 'self-hosted');
|
||||
assert.equal(features.restrictedManagedDeployment, false);
|
||||
assert.equal(features.mediaProxyEnabled, false);
|
||||
assert.equal(features.iptvEnabled, true);
|
||||
assert.ok(features.restrictionSummary);
|
||||
});
|
||||
});
|
||||
|
||||
test('media proxy mode takes precedence over legacy boolean alias', () => {
|
||||
withRuntimeEnv({
|
||||
VERCEL: '1',
|
||||
KVIDEO_MEDIA_PROXY_MODE: 'disabled',
|
||||
KVIDEO_MEDIA_PROXY_ENABLED: 'true',
|
||||
}, () => {
|
||||
const features = getRuntimeFeatures();
|
||||
|
||||
assert.equal(features.mediaProxyEnabled, false);
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,10 @@ compatibility_flags = ["nodejs_compat", "global_fetch_strictly_public"]
|
||||
# wrangler secret put AUTH_SECRET
|
||||
# wrangler secret put UPSTASH_REDIS_REST_URL
|
||||
# wrangler secret put UPSTASH_REDIS_REST_TOKEN
|
||||
#
|
||||
# Optional iOS/Safari HLS compatibility mode:
|
||||
# set KVIDEO_MEDIA_PROXY_MODE=enabled
|
||||
# Public unauthenticated proxy access still requires KVIDEO_PUBLIC_RELAY_ENABLED=true.
|
||||
|
||||
[assets]
|
||||
directory = ".open-next/assets"
|
||||
|
||||
Reference in New Issue
Block a user