# go-emby2openlist takes the video traffic off your Emby server

> go-emby2openlist is a Go reverse proxy that sits between Emby clients and an Emby server, rewriting media requests into direct links served by OpenList, so the bytes travel from the cloud drive to the client instead of through the server. It goes to unusual lengths for Aliyun drive transcoding, at the cost of fixed cache windows and a set of documented subtitle caveats.

**AmbitiousJun/go-emby2openlist** — Go 语言编写的 Emby + OpenList (Alist) 网盘直链反向代理服务，深度适配阿里云盘转码播放，支持 OpenList 本地目录树生成，配置简单，DockerCompose 一键部署

- Repository: https://github.com/AmbitiousJun/go-emby2openlist
- Stars: 670 · Forks: 71
- Language: Go
- License: GPL-3.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/ambitiousjun-go-emby2openlist

## The same Emby server, a different data path

go-emby2openlist is a reverse proxy written in Go for Emby together with OpenList, the drive listing service also known as Alist. Its stated purpose is to adapt cloud drive playback, with particular attention to Aliyun drive transcoded playback, and to be easy to deploy with Docker Compose.

The problem it solves is the data path, and the project documentation draws the two paths out explicitly. In a normal setup, the client talks to the Emby server, the server reads through a disk mount service, that service talks to OpenList, and OpenList talks to the drive. The video bytes cross the server, so the maximum load speed a viewer sees is the server's upload bandwidth, and the server has to be strong enough to transcode or remux for 4K playback.

With the proxy in place, the ordinary API calls are simply forwarded to the source Emby server and cached where it makes sense, so the client cannot tell the difference. The video requests are different. The proxy asks OpenList for a direct link and answers the client with a redirect, and the client then pulls the video from the drive itself. The server is out of the picture for the bulk of the data, which means the viewer gets the full speed of the drive link and the decoding happens on the client, so whether 4K is watchable depends on the hardware of the television box rather than the server.

The tool is written in Go, released under the GPL-3.0, and published as a Docker image. The last push to the repository was on 2026-09-29, with v2.9.0 tagged the same day, following v2.8.2 on 2026-08-15 and v2.8.1 on 2026-07-18.

## Aliyun transcoded links are rewritten through PlaybackInfo

The feature that goes furthest is transcoded direct link playback for Aliyun drive, and it comes with the clearest limits.

The project states that the transcoded link is already good enough to use normally, that Emby Web, Emby for AndroidTV and most other clients can play it, and that playback does not break because the direct link expires. The mechanism is described as proxying the request and modifying the PlaybackInfo interface, which is the Emby API response that tells a client how to play an item. By rewriting that response, the proxy can point a client at a transcoded stream on the drive instead of at Emby's own transcoding pipeline.

The cost of that rewrite is time. A transcoded resource request through the proxy takes roughly two to three seconds, and the project has used Go's concurrency to make the handling asynchronous so that a fast path can complete in about a second. The result is cached for a fixed twelve hours, and the documentation says that value may be adjusted later if problems appear.

Two limits are stated plainly. If a video has several embedded audio tracks, the transcoded direct link only plays the default one. And embedded subtitles are lost, although if transcoded subtitles exist they are adapted into the PlaybackInfo information for the transcoded version.

Whether a third party traffic package is consumed is answered with a no, and whether non-members are throttled is left as something for the reader to test.

## Three fixed cache windows do most of the work

Caching is the other half of the project, and the documentation is unusually specific about the time windows, which tells you the project is tuned around a specific set of trade-offs.

Direct link caching is fixed at ten minutes, and the reason given is compatibility with Aliyun drive. Other drives have not been tested in this respect, so the same setting is applied to them without evidence that it is right.

Subtitle caching is fixed at thirty days. There is an important caveat attached to it: Emby cannot currently be stopped from reading subtitles from the locally mounted file, so on the first play of a subtitled video, Emby calls FFmpeg to extract the subtitle from the local file and then caches it. The first extraction is slow, potentially a long wait before subtitles appear, and the documentation suggests using a third party player such as MX player or Fileball to avoid it. During that first play, server traffic is still consumed.

The large interface cache is the twelve hour one described above, covering the PlaybackInfo calls that are expensive to regenerate. There is also a general cache middleware for ordinary API responses, and the project states that in practice it is no worse than connecting straight to the source server.

Other plumbing in the same list is a websocket proxy, and a client anti-transcode feature that stops the client from re-encoding the media into another container.

## The client table is the real compatibility spec

The project publishes a table of clients that have been tested, with the last tested version and separate columns for original quality playback and Aliyun transcoded playback. It is the most useful part of the documentation and the part most likely to change what you would do.

Gemby is listed at v2.9.0 and works for both. Emby Web at 4.8.8.0 works for both, with two notes: transcoded subtitles have a chance of failing to mount, and original quality subtitles can be mounted. Emby for Android at 3.4.23 works for both. Emby for AndroidTV at 2.0.95g works for both, with a caveat that seeking with the remote control can trigger the direct link server's rate limit and briefly make the video unplayable, and that subtitles cannot be mounted in the transcoded path.

The entries with question marks rather than ticks are the honest ones. Emby for iOS and Emby for macOS are marked as untested for both columns, with the note that a premium subscription was not available to test with.

Other clients have specific edges. Infuse works for original quality but not for Aliyun transcoding, and setting its cache method to not cache is said to prevent the frequent request problem. VidHub was only tested up to version 1.0.7. Fileball and Stream Music are listed as working. The table also has a column for additional notes, which is where the caveats live.

## Deploying with compose, and what the config file holds

The Docker Compose file is the documented one-command deployment path, and it shows the shape of the service:

```yaml
services:
  go-emby2openlist:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - TZ=Asia/Shanghai
      - GIN_MODE=release
```

Two ports are published, HTTP on 8095 and HTTPS on 8094, and the same pair is exposed by the Dockerfile:

```yaml
    ports:
      - 8095:8095 # http
      - 8094:8094 # https
```

The volume list is where the behaviour is configured from the filesystem. A config.yml is mounted at /app/config.yml, and the project ships config-example.yml as the starting point, so the configuration is a YAML file rather than environment variables. Alongside it are directories for the SSL certificates, for custom-js and custom-css, which back the two injection features for the web interface, a lib directory, and openlist-local-tree, which is where the generated local directory tree for OpenList is written.

Two commented proxy variables are present in the compose file, HTTP_PROXY and HTTPS_PROXY pointing at a local port, for users who need to reach the drive through a proxy.

The proxy also generates a local directory tree from OpenList, and can inject your own JavaScript and CSS into the web client, which is how the two customisation features are implemented.

## A static Go binary behind a Node built interface

The build is a three stage Dockerfile, and the module dependencies explain what the service actually does.

The interface is a separate front end built with Node in the first stage, from a node:24-alpine base with npm ci and npm run build, and the resulting client bundle is copied into the Go build. The second stage compiles the binary from a golang:1.26-alpine image, with the module proxy set to goproxy.cn and the result built as a static binary with CGO disabled:

```dockerfile
RUN CGO_ENABLED=0 go build -tags=goexperiment.jsonv2 -a -installsuffix cgo -ldflags="-X main.ginMode=release" -o main .
```

The runtime stage is alpine:latest with timezone data installed, and the entry point is the binary itself.

The module file pins Go 1.26.3 and lists a short direct dependency list. The web framework is gin, the websocket library is gorilla/websocket, the configuration is parsed with gopkg.in/yaml.v3, concurrency primitives come from golang.org/x/sync, and bogem/id3v2 handles ID3 tags, which is what lets the proxy rewrite the metadata of a direct link before the client sees it.

The repository layout matches a standard Go service with a web front end attached: cmd/ and internal/ for the backend, main.go at the root, web/ for the front end, build.sh and build_web.sh for the build, plus the ssl/ directory for certificates.

## An easy to run tool with a documented off ramp

The project states its own intent clearly: it is meant to be easy and light, friendly to beginners, and deeply adapted to Aliyun drive. It also says that if you use it with other drives and hit problems, feedback in the issue area is welcome.

For a more complex setup it points somewhere else. The documentation recommends a more fully featured reverse proxy service, embyExternalUrl by bpking1, for users with more demanding requirements. That is an honest boundary statement from the author, and it is the fastest way to decide whether this project fits, since the difference between the two is the difference between a tool tuned for one drive and one designed for general use.

The other boundary is the one the subtitle behaviour creates. A first play of a subtitled file still goes through the server, because Emby extracts the subtitle with FFmpeg from the local mount, and that extraction can take a long time. For a library where most files have subtitles, that first-play cost is a real cost, and the documentation's own suggestion is to use a third party player.

On maintenance, the repository is not archived, the last push was on 2026-09-29, and v2.9.0 was tagged on the same day with the Docker image published under the ambitiousjun/go-emby2openlist name. The licence is the GPL-3.0, so anyone modifying it and distributing the result has to carry the copyleft terms forward, which is worth knowing before you fork it into an internal deployment.

## Conclusion

go-emby2openlist is the right choice for a home server operator whose Emby instance reads from a cloud drive mounted through OpenList and whose uplink is the bottleneck, and the wrong choice for a complex deployment, since the project itself points users with more demanding needs to embyExternalUrl. Before adopting it, check the client support table for your own player, and read the subtitle behaviour, because a first play of a subtitled file still pulls subtitle data through the server and can take a long time.

## FAQ

### What does go-emby2openlist actually do?

It is a Go reverse proxy placed between Emby clients and an Emby server. Ordinary API calls are forwarded to the source server, while video requests are turned into direct links from OpenList and answered with a redirect, so the video data travels from the drive to the client instead of through the server.

### How do I deploy go-emby2openlist?

The project ships a Docker Compose file that builds the image from the Dockerfile and publishes port 8095 for HTTP and 8094 for HTTPS. Configuration is a config.yml mounted into the container, with config-example.yml provided as the starting point, alongside directories for SSL certificates, custom JavaScript, custom CSS and the generated OpenList local tree.

### What are the limitations of the Aliyun drive transcoded playback?

The documentation states that a video with several embedded audio tracks only plays the default audio track in the transcoded direct link, and that embedded subtitles are lost, although transcoded subtitles are adapted into the PlaybackInfo response. The transcoded link is also documented as not being guaranteed against throttling for non-members.

### Why do subtitles take so long on the first play?

Emby cannot yet be prevented from reading subtitles from the locally mounted file, so on the first play it calls FFmpeg to extract them and then caches them, which is slow and still consumes server traffic. The cached subtitles are then reused for thirty days, and the project suggests using a third party player such as MX player or Fileball to avoid the wait.

### Which Emby clients are known to work with go-emby2openlist?

The project publishes a table of tested clients with their last tested versions, covering Gemby, Emby Web, Emby for Android, Emby for AndroidTV, Fileball, Infuse, VidHub and Stream Music, with separate columns for original quality and Aliyun transcoded playback and notes on known problems.

## Sources

- [AmbitiousJun/go-emby2openlist on GitHub](https://github.com/AmbitiousJun/go-emby2openlist)
- [Issues](https://github.com/AmbitiousJun/go-emby2openlist/issues)
- [License: GPL-3.0](https://github.com/AmbitiousJun/go-emby2openlist/blob/main/LICENSE)
- [README](https://github.com/AmbitiousJun/go-emby2openlist/blob/main/README.md)
- [Releases](https://github.com/AmbitiousJun/go-emby2openlist/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ambitiousjun-go-emby2openlist
