Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Repository Guidelines

## 项目结构与模块职责

- `cmd/plex-gateway/` 是生产入口和依赖装配层,`cmd/plex-probe/` 提供只读 Plex 行为探测。
- `internal/gateway/` 负责 HTTP 路由、Plex 反向代理、Direct Play 适配和响应处理。
- `internal/mediainfo/`、`plexmeta/`、`playback/`、`resolver/`、`pathmap/`、`database/` 等包分别管理分析、协议、播放、解析、路径和持久化职责。
- 测试与源码同目录,统一使用 `*_test.go`。架构、兼容性、性能和发版文档位于 `docs/`。
- 部署模板为 `docker-compose.example.yml` 和 `app.env.example`,运维工具位于 `scripts/`。

## 构建、测试与本地开发

```sh
go run ./cmd/plex-gateway # 使用环境变量启动 Gateway
go test ./... # 运行完整测试
go test -race -count=1 ./... # 与 CI 一致的竞态检查
go vet ./... # Go 静态检查
go build ./cmd/plex-gateway # 构建二进制
docker build -t plex-gateway:dev .
```

聚焦测试可使用 `go test ./internal/gateway -run TestName`。性能基准与验收条件见 `docs/performance-matrix.md`。

## 编码风格与命名

所有 Go 文件必须通过 `gofmt`。包名使用简短的小写职责名。导出标识符应有 Go doc;注释用于说明协议契约、不变量、兼容边界和失败策略,不复述代码。保持现有包边界,避免混合 Gateway、MediaInfo 和 Plex 协议职责。

## 测试要求

使用标准库 `testing`。测试命名为 `TestBehavior`,基准命名为 `BenchmarkOperation`。XML/JSON、客户端兼容和配置组合优先使用表驱动测试。协议改动应覆盖成功、异常输入、取消、超时和 fail-open;涉及授权、请求身份、缓存或并发时必须运行 race 测试。性能验收必须区分冷缓存、热缓存、本地媒体、STRM、快速切换和多客户端场景;请求成功不等于性能达标。

## 提交与 Pull Request

提交遵循简洁的 Conventional Commits,例如 `feat: support Plex Web direct play`、`fix: trust Plex materialized media info`。在主题分支开发。PR 说明应包含用户可见结果、兼容性边界、验证结果和剩余限制;除非界面变化需要说明,否则不要求截图。

## 文档与配置

`README.md` 面向安装和使用者,只说明能力、配置、兼容性和限制;实现原理、取舍与性能证据放入 `docs/`。运行默认值以代码为准,`app.env.example` 提供完整中文说明,Compose 示例只保留必要挂载与 `env_file`,不得写入特定 NAS 路径、域名或私有拓扑。

## 变更日志与发版说明

`CHANGELOG.md` 是给用户看的中文发版说明,不是代码实现记录。按重要性描述新增能力、体验变化和已知限制,最重要的用户功能必须放在最前。不要堆砌 Stream ID、投影层级、缓存、队列或 ffprobe 等内部细节,除非它们直接影响用户配置或使用边界。

`CHANGELOG.en.md` 必须同步表达相同内容。发版时同时更新 `VERSION` 和两个 changelog 的带日期版本章节;普通开发不要提前修改版本号。发版前比较上一个版本 Tag 到当前 HEAD,确认所有用户可见变化均已覆盖,且没有把 roadmap 或未交付能力写成已发布功能。发布源只使用 `main` 上的 `VERSION`;手工 Release 仅用于按当前版本重建,不接受自定义版本或源码 ref。Push、PR、Tag、镜像和 Release 等外部动作必须遵守当前维护者授权边界。

## 架构、兼容性与性能边界

Plex 负责媒体库、认证和观看状态;Gateway 只适配控制流和 302,不代理媒体字节;MediaVault 负责 STRM 和直链解析。所有改动必须保持本地媒体透明代理,Part、universal start 和 302 路径不得同步执行 SQLite、ffprobe 或其他分析 I/O。

客户端兼容改动应先比较 Plex 对本地媒体与 STRM 的原始响应,并按协议语义适配。只有真实请求证明 Plex 本身存在客户端差异时,才允许增加客户端分支。Gateway 只补缺失的描述性 MediaInfo,不伪造 Plex Stream ID 或播放选择状态;Plex 已返回物化 Stream 行时必须原样透传并跳过重复探测。

实验能力必须通过独立开关隔离。关闭时不得创建额外状态、缓存、锁或远程 I/O。未来 Plex MediaInfo 持久投影必须作为隔离组件,具备 PMS/schema 检测、备份、回滚和 API 回读,不能进入 Gateway 热路径。不得提交真实凭据、私有地址、媒体名称或未脱敏抓包。
17 changes: 14 additions & 3 deletions CHANGELOG.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.3] - 2026-08-30

### Added

- Plex Web can Direct Play browser-native STRM media while video remains a
browser-to-CDN 302 path.
- Plex Web can now Direct Play browser-native STRM media. Playback, pause,
resume, and seek are verified. Sources that require DASH, remuxing, or
audio/video transcoding remain unsupported.

### Improved

- Improved Plex iOS stability when opening STRM libraries, seasons, and item
details, and removed an incorrect main-feature label.
- Improved 4K, HEVC, HDR, Dolby Vision, and audio technical labels for STRM
media while preserving information already provided by Plex.

## [0.1.2] - 2026-08-30

Expand Down Expand Up @@ -75,7 +85,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
gateway redirects playback.
- Logs and metrics exclude Plex tokens and complete signed media URLs.

[Unreleased]: https://github.com/InfinityPacer/plex-gateway/compare/v0.1.2...HEAD
[Unreleased]: https://github.com/InfinityPacer/plex-gateway/compare/v0.1.3...HEAD
[0.1.3]: https://github.com/InfinityPacer/plex-gateway/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/InfinityPacer/plex-gateway/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/InfinityPacer/plex-gateway/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/InfinityPacer/plex-gateway/releases/tag/v0.1.0
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@

## [未发布]

## [0.1.3] - 2026-08-30

### 新增

- 支持 Plex Web 直接播放浏览器原生兼容的 STRM,视频仍由浏览器通过 302 直连 CDN。
- 支持 Plex Web 直接播放浏览器原生兼容的 STRM,已验证播放、暂停、继续和拖动进度。
需要 DASH、转封装或音视频转码的片源仍不支持。

### 改进

- 改善 Plex iOS 打开 STRM 媒体库、剧集和详情页的稳定性,修复错误的“正片”标识。
- 改善 STRM 的 4K、HEVC、HDR、Dolby Vision 和音频技术标签显示,并保留 Plex 已有信息。

## [0.1.2] - 2026-08-30

Expand Down Expand Up @@ -63,7 +71,8 @@
- Gateway 返回云端播放重定向前,由 Plex 使用当前客户端凭据授权对应 Part。
- 日志和指标不包含 Plex Token 或完整的签名媒体 URL。

[未发布]: https://github.com/InfinityPacer/plex-gateway/compare/v0.1.2...HEAD
[未发布]: https://github.com/InfinityPacer/plex-gateway/compare/v0.1.3...HEAD
[0.1.3]: https://github.com/InfinityPacer/plex-gateway/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/InfinityPacer/plex-gateway/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/InfinityPacer/plex-gateway/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/InfinityPacer/plex-gateway/releases/tag/v0.1.0
30 changes: 24 additions & 6 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ fully transparent Plex Web shell.
| --- | --- | --- |
| Local Plex media | Supported by transparent proxy | The gateway does not rewrite local Parts. |
| Infuse Direct Play | Direct Play verified | Uses the Plex Part redirect path. |
| Plex iOS ExperimentalPlayer | Direct Play verified | STRM uses the universal decision/start redirect path. |
| Plex iOS ExperimentalPlayer | Direct Play verified | STRM uses the universal decision/start redirect path. Descriptive Gateway Streams can provide 4K, HEVC, HDR/DV, and related technical fields; audio state or playback badges may remain incomplete until Plex has materialized native Stream rows. |
| Plex for Apple TV | Direct Play verified | HDR STRM uses the universal decision/start redirect path. Apple TV 4K supports Dolby Vision Profile 5 Direct Play, while Plex version, container, and source combinations may still affect compatibility; the gateway does not reject it by default. |
| Plex Web cloud Direct Play | Direct Play verified | MP4/H.264/AAC was verified for initial playback, pause, resume, and seek with CDN-direct delivery. Other containers and codecs depend on native browser support; sources that require DASH, remuxing, or transcoding remain unsupported. |

Expand Down Expand Up @@ -201,11 +201,29 @@ are used to create descriptive video, audio, and subtitle Streams with fields
such as HDR10, Dolby Vision, bit depth, codec, codecID, bitrate, channel layout, and
language code. Generated Streams never contain Plex Stream IDs or playback
selection fields such as `selected`, `default`, or `decision`. When Plex already
has Streams, only missing fields on identity-matched Streams are filled. Missing
sibling Streams are not created and existing Plex values are not overwritten.
This release writes fallback probe results only to the gateway's SQLite store and
does not write to the Plex DB. Production Plex DB writes are a next-stage
evaluation after coverage, compatibility, backup, and rollback validation.
has descriptive Streams that are not materialized in its database, only missing
fields on identity-matched Streams are filled. Missing sibling Streams are not
created and existing Plex values are not overwritten.

Descriptive Streams provide technical labels but cannot replace native Stream
rows in the Plex data model. Real-client A/B testing confirmed that the official
iOS client can display fields such as 4K and HEVC without Plex Stream IDs and
playback-selection state, while audio may still appear absent and playback-page
technical badges may remain incomplete. Synthesizing IDs is unsafe because
clients may use them to select tracks. When a STRM Media/Part shape is complete,
the Part carries the real media size, and every Stream has a positive ID assigned
by Plex, no further Gateway projection is required. Metadata and Direct Play
decisions are returned byte-for-byte without reading the STRM, consulting the
MediaInfo cache, or admitting ffprobe. Missing optional descriptions such as
`language` or `title` do not change that decision. Non-STRM local Parts always
remain outside this path and are passed through unchanged.

This release writes fallback probe results only to the gateway's SQLite store
and does not include a Plex DB writer. A single-item offline proof of concept
confirmed that native Plex Stream rows restore complete audio and technical
presentation in the official iOS client and automatically select the Gateway's
byte-for-byte passthrough path. Production writes still require coverage,
PMS/schema compatibility, backup, and rollback validation.

The L1 hot path returns without synchronously reading SQLite for the request.
Access renewal may touch SQLite asynchronously, so persistence I/O stays out of
Expand Down
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Gateway。需要 DASH、转封装或转码的片源仍不支持,Gateway 不合
| --- | --- | --- |
| Plex 本地媒体 | 透明代理支持 | Gateway 不会重写本地 Part。 |
| Infuse Direct Play | 已验证 Direct Play | 使用 Plex Part 重定向路径。 |
| Plex iOS ExperimentalPlayer | 已验证 Direct Play | STRM 使用通用 decision/start 重定向链路。 |
| Plex iOS ExperimentalPlayer | 已验证 Direct Play | STRM 使用通用 decision/start 重定向链路。Gateway 描述性 Stream 可补充 4K、HEVC、HDR/DV 等技术字段;Plex 尚未物化原生 Stream 行时,音轨状态或播放页徽标仍可能不完整。 |
| Plex for Apple TV | 已验证 Direct Play | HDR STRM 使用通用 decision/start 重定向链路。Apple TV 4K 支持 DV Profile 5 Direct Play,但 Plex 版本、容器和片源组合仍可能影响兼容性;Gateway 默认不拒绝。 |
| Plex Web 云端 Direct Play | 已验证 Direct Play | MP4/H.264/AAC 样本已验证首播、暂停、继续和 seek,并保持 CDN 直达;其他容器与编码取决于浏览器原生能力,需要 DASH、转封装或转码的片源不支持。 |

Expand Down Expand Up @@ -173,9 +173,22 @@ Media、Part 和 Stream 技术字段。若 Plex 的 Part 完全没有 Stream,G
ffprobe 的流类型和源索引创建描述性的视频、音频和字幕 Stream,并投影 HDR10、Dolby
Vision、bit depth、codec、codecID、bitrate、声道布局和语言码等字段。合成 Stream 不包含 Plex
Stream ID,也不生成 `selected`、`default` 或 `decision` 等播放选择字段。Plex 已经存在
Stream 时只补充身份匹配 Stream 的缺失字段,不创建缺少的其他流,也不覆盖 Plex 值。
当前版本只将兜底探测结果写入 Gateway SQLite,不写入 Plex DB。Plex DB 生产写入属于下一
阶段评估内容,需先完成覆盖率、兼容性、备份和回滚验证。
但尚未物化到数据库的描述性 Stream 时,只补充身份匹配 Stream 的缺失字段,不创建缺少
的其他流,也不覆盖 Plex 值。

描述性 Stream 能提供技术标签,但不能代替 Plex 数据模型中的原生 Stream 行。真实客户端
A/B 已确认:没有 Plex Stream ID 和播放选择状态时,官方 iOS 客户端可以显示 4K、HEVC 等
部分标签,但音频可能显示为“无”,播放页技术徽标也可能缺失;这不是继续伪造 ID 能安全
解决的问题,因为客户端会使用这些 ID 选择音轨。若 STRM 的 Media/Part 技术字段完整、Part
已具有真实媒体大小,且所有 Stream 都带有 Plex 数据模型分配的正数 ID,则无需继续执行
Gateway 投影。metadata 和 Direct Play decision 会字节级原样返回,不读取 STRM、不查询
MediaInfo 缓存,也不发起 ffprobe;`language`、`title` 等可选描述字段缺失不会改变该判断。
本地非 STRM Part 始终完全透传,不进入上述判断。

当前版本只将兜底探测结果写入 Gateway SQLite,不写入 Plex DB。单样本离线 PoC 已确认,
Plex 持有原生 Stream 行后,官方 iOS 客户端可恢复完整音轨和技术展示,Gateway 也会自动
进入上述纯透传路径。生产写入仍需完成覆盖率、PMS/schema 兼容、备份和回滚验证,本版本
不包含 Plex DB 写入器。

L1 热路径直接返回,不会为了本次请求同步读取 SQLite。访问续期可能异步 touch SQLite,
不会把持久化 I/O 放进请求关键路径。热缓存投影可以并发执行;metadata 浏览冷 miss 立即
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.2
0.1.3
38 changes: 31 additions & 7 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,25 @@ the bounded P2 scheduler while the original Plex response is returned
immediately. Successful worker results populate L1 and SQLite for later
responses.

Projection bypass is derived only from the current Plex response, not from the
identity of a database writer. Local non-STRM Parts bypass MediaInfo logic. For a
STRM Part, complete Media/Part technical fields, a real media size, and positive
IDs on every Stream prove that the Stream rows are materialized in the Plex data
model. Such item metadata and Direct Play decisions are preserved byte-for-byte
and do not read the STRM, cache, resolver, or probe worker. Optional descriptions
such as language and title are not bypass requirements.

Gateway-generated descriptive Streams intentionally omit Plex IDs and playback
selection state. They can project codec, resolution, HDR/Dolby Vision, bitrate,
channel, and language facts, but they are not equivalent to materialized Plex
Stream rows. Real-client A/B showed that the official iOS client may display the
Media-level 4K and HEVC labels while still showing no audio or omitting a
playback-page technical badge. IDs are not synthesized because clients may use
them for track selection. A single-item offline Plex DB proof of concept restored
the native Stream shape and made the Gateway choose byte-for-byte passthrough;
this is evidence for a future isolated writer, not a production write path in
this release.

## MediaInfo scheduling contract

The in-memory scheduler owns all remote-probe admission. It has three fixed
Expand Down Expand Up @@ -288,8 +307,11 @@ and helper endpoint.
This compatibility path does not expand the media data plane. The browser still
downloads bytes directly from the final CDN. It only supports containers and
codecs the browser can Direct Play. If Plex Web needs DASH, remuxing, or video
transcoding, an ordinary MP4 or MKV response is not a manifest and this helper
cannot make that path playable. The gateway does not proxy media, package
or audio transcoding, an ordinary MP4 or MKV response is not a manifest and this
helper cannot make that path playable. A real A/B confirmed this boundary: Plex
served a local item through DASH with copied video and transcoded audio, while a
STRM `start.mpd` redirect exposed the original MP4 where the browser expected a
DASH manifest and ended with `s1002`. The gateway does not proxy media, package
segments, or synthesize manifests. Local media retains Plex's normal Web
compatibility.

Expand Down Expand Up @@ -322,11 +344,13 @@ is accepted, the body is not consumed, and every error preserves the other
probe fields. This fallback remains inside the MediaInfo worker and never enters
the synchronous 302 path.
A stable MediaVault or MoviePilot provider can replace remote probing without
changing playback. Plex DB production writing is a next-stage evaluation only,
covering an official API, another supported PMS interface, and an isolated
database helper with explicit coverage, backup, and rollback tests. This release
does not select or enable a Plex DB write path. Intro and credits work starts
with an isolated Plex marker API probe. See
changing playback. A production Plex projection writer remains an explicit
evaluation across an official API, another supported PMS interface, and an
isolated database helper with coverage, version/schema allowlists, consistent
backup, rollback, compare-and-set identity checks, and API readback. The
single-item offline database proof of concept establishes client value and the
Gateway bypass contract only; this release does not select or enable a writer.
Intro and credits work starts with an isolated Plex marker API probe. See
[media-lifecycle-architecture.md](media-lifecycle-architecture.md).

Performance acceptance and reproducible microbenchmarks are defined in
Expand Down
10 changes: 10 additions & 0 deletions docs/media-lifecycle-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,14 @@ ffprobe。
Dolby Vision 映射必须使用同一片源的本地文件与 STRM Plex response 建立 fixture,不能
仅按 ffprobe 字段名猜测。

描述性 Stream 与 Plex 原生 Stream 行不是同一能力。真实客户端 A/B 已确认,只有
Gateway 描述性字段时,官方 iOS 客户端可以消费 Media 层的 4K、HEVC 等标签,但音轨状态
和播放页技术徽标仍可能不完整。Gateway 不能伪造 Stream ID,因为客户端可能使用它选择
音轨。当前 Plex response 已包含完整 Media/Part、真实文件大小,并且每个 Stream 都有
正数 ID 时,Gateway 必须将其视为“无需继续投影”,字节级透传并跳过 STRM、缓存和探测。
单样本离线写库 PoC 已验证原生 Stream 行可以恢复官方客户端完整展示,并触发该纯透传
路径;这只证明方案价值,不代表生产 Helper 已完成。

## 12. Gateway 边界

Gateway 对所有来源使用同一播放契约:
Expand Down Expand Up @@ -781,6 +789,8 @@ provider、目标 backing、decision revision、人工确认或策略依据,
- 比较 Plex 官方 API、其他受支持 PMS 接口和独立 Plex Helper,不提前固定写入方案;
- Helper 候选只在 Plex 停止或经过验证的维护窗口执行;
- 测试库验证一致性备份与 restore、schema allowlist、CAS、写入和 API 回读;
- 保留单样本离线写库 PoC 结论:Plex 原生 Stream 行恢复官方客户端音轨和技术展示,
Gateway 自动停止重复投影和探测;不得把该结论扩大为生产写入已交付;
- 验证刷新、Analyze、PMS 重启和升级后的保持/恢复;
- 验证 Apple TV 和其他目标客户端是否消费 Gateway enrichment 或 Plex 持久字段。

Expand Down
Loading