# dycast: a Douyin live room danmaku reader you can forward to your own backend

> dycast connects to a Douyin live room, parses the binary protobuf frames the platform sends, and shows the danmaku in a three column browser interface, with the option to forward every message to a WebSocket endpoint of your own. The hard part is not the UI, it is the signature computation and the hand written proto files the project carries.

**skmcj/dycast** — 抖音弹幕姬，只需输入房间号，即可实时获取对应直播间的弹幕信息，并可将其转发到自己的后端服务

- Repository: https://github.com/skmcj/dycast
- Stars: 852 · Forks: 212
- Language: TypeScript
- License: not declared
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/skmcj-dycast

## The two hard problems are the wss signature and the protobuf payload

dycast is a small work that fetches danmaku from a Douyin live room. The user enters a room number, the program gets the live danmaku in real time, parses it, shows it, and can forward it to the user's own backend over a ws or wss address for uses such as interactive danmaku games or data analysis.

The project names the two difficulties it had to solve, and they are the right two to name. The first is computing the wss link for the live room. The second is parsing the binary danmaku data that arrives.

For the first, the reasoning starts from a browser network panel: open a live room, and there is a ws request that is the real-time danmaku connection. Its protocol address contains the most important parameter, a signature, which has to be computed from the roomId and the uniqueId. The project states that this is derived through debugging and reverse engineering, and that the calculation function is packaged in src/core/signature. Once the signature is known, it is combined with roomId and uniqueId to produce the complete wss link, and the connection is established.

For the second, the frames that arrive are binary. The protocol is protobuf, so a matching proto file is required, and the project explains that the proto structure was reconstructed by studying the objects the platform parses internally. The suggested search terms are PushFrame and WebcastChatMessage. The proto file is then compiled with protoc or a similar tool, in this project into TypeScript via protobufjs, with one modification: Long values are changed to strings.

```sh
# 参考命令
pbjs --ts model.ts model.proto
```

The generated model.ts is what the project imports.

None of this is a public API. That is the single most important fact for anyone considering the project.

## The parse chain is PushFrame, then gzip, then Response, then Message

The parsing pipeline is documented step by step, and the sequence is the kind of detail you normally only learn by reading someone's source.

A frame taken from the websocket is first parsed as a PushFrame. Its payload is still binary data and it is gzip compressed, so it is decompressed and then parsed as a Response. The messages array of that Response holds the actual messages, each of type Message, and each Message payload is the specific danmaku body. The main payload types named in the documentation are ChatMessage, MemberMessage and LikeMessage.

The pako dependency in the project manifest is what performs the gzip decompression, and protobufjs handles the proto structures.

The message categories the tool displays are listed in the feature section, and they map onto those proto types. Chat danmaku covers plain text, ordinary emoji, member emoji and merged emoji. Gift danmaku requires a login cookie. There are also follow danmaku, like danmaku carrying the like count, enter danmaku, and a catch-all for other information such as connection status messages. Live room information is shown too, such as the audience count.

The forwarding format is JSON. The documentation is explicit that the raw data contains everything Douyin sends, but the tool only extracts and wraps a subset into the message structure it forwards, and the file to change if you want more fields is src/core/dycast.ts.

That file reference is the most useful pointer in the whole document. If you need a field the tool drops, you are not stuck waiting for a feature request; you open one file and change the wrapper.

## The forwarded message keeps a fixed envelope of user, gift and room data

The shape of the forwarded payload is defined as TypeScript interfaces, which means the forwarding contract is visible in the source rather than implied.

```typescript
export interface DyMessage {
  // 弹幕 ID
  id?: string;
  // 弹幕类型
  method?: CastMethod;
  // 用户信息
  user?: CastUser;
  // 送给用户
  toUser?: CastUser;
```

The envelope carries the danmaku id, the type, the sending user, a recipient user for gifts, gift information, the text content, rich text content, room information and a gift leaderboard. Every field is optional, which is a deliberate choice: a message that is a like carries no gift, and a chat message carries no leaderboard.

The room object holds the live room state, and its counters are typed as number or string rather than number alone, which reflects the Long handling described earlier.

```typescript
export interface LiveRoom {
  audienceCount?: number | string;
  likeCount?: number | string;
  followCount?: number | string;
  status?: number;
}
```

The user object is worth reading closely, because the comments in it name the exact proto paths the values come from. The id is user.sec_uid or user.id_str, the name is user.nickname, and the avatar is user.avatar_thumb.url_list.0. The gender field is marked as a guess, with 0, 1 and 2 meaning unknown, male and female.

The message type is an enum whose values are the Webcast proto message names, so a consumer of your backend can switch on the type without string matching.

```typescript
export enum CastMethod {
  CHAT = 'WebcastChatMessage',
  GIFT = 'WebcastGiftMessage',
  LIKE = 'WebcastLikeMessage',
  MEMBER = 'WebcastMemberMessage',
  SOCIAL = 'WebcastSocialMessage',
```

The enum continues through room user sequence, control, room rank, room stats, emoji chat, fans club and room data sync messages, and ends with a CUSTOM entry for messages the tool defines itself.

## The browser UI is a three column layout with a room number and a forward address

The interface description is specific enough to be useful. The layout has three columns. The left column shows live room information and the connection status. The middle column is the main danmaku display. The right column holds input fields and other information.

The right column has two input boxes, a room number and a forward address, and both have format validation, so an incorrect format cannot be connected.

The danmaku list has a column of icon buttons on its right edge, one per danmaku type currently shown, and clicking a button toggles that type's visibility. That is a small design decision that matters more than it sounds: on a busy room the danmaku volume is high, and being able to hide the like spam is what makes the feed readable.

The flow is: type the room number, click connect, wait a few seconds, and the status area at the lower left shows the result. The documentation warns that network congestion sometimes occurs and you should connect again later, and that both success and failure produce a message notification, and that the console output carries the same information. Once connected, you paste your own WebSocket server address into the forward field and click forward, which establishes the connection and pushes danmaku to your backend in real time.

The three column arrangement is built with Vue rather than Svelte or React, and the manifest pins vue at 3.5.13 with vue-virtual-scroller for the list, which is what keeps a fast moving danmaku list from turning into a rendering bottleneck. There is also a scroller variant for Vue 3 maintained as a separate package, and pako for the gzip work, plus sass for styling.

## Building it is a Vite project, and serving it needs an nginx proxy to Douyin

The project is a Vite application with Vue, and the three commands are the standard ones.

```sh
npm install
```

```sh
npm run dev
```

```sh
npm run build
```

The manifest shows why the build script looks the way it does. The build script runs a type check and the build in parallel with npm-run-all2, the build-only script is vite build, the type check is vue-tsc --build, and the preview script is vite preview. The dev script is just vite, and the package manager field of the project is pnpm with a pnpm-lock.yaml at the root, so pnpm is the intended installer even though npm works.

The deployment story is where this project gets interesting, and it is not a static file drop. Because the front end has to reach Douyin's live endpoints, and because browsers will not let a page make those cross-origin requests freely, the documentation provides an nginx configuration that proxies two paths.

The first is the API path, proxied to the live site.

```nginx
location /dylive {
    proxy_pass https://live.douyin.com/;
```

The second is the websocket path, and the comment explains a subtlety: nginx does not distinguish ws from wss, WebSocket is really an HTTP upgrade, so the upstream is written as https rather than wss.

```nginx
location /socket {
    proxy_pass https://webcast100-ws-web-lq.douyin.com/;

    # WebSocket 关键配置
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
```

The rest of the configuration is about making the proxy look like a desktop browser to the platform. The Host and Referer headers are rewritten to live.douyin.com, the Origin header is set to the same, and there is a User-Agent substitution that detects iPhone, iPad, Android and mobile strings and swaps in a desktop Chrome string, with the comment explaining that this prevents a mobile redirect. Cookie handling is also dealt with: the Domain is cleared so the cookie applies to the current host, the Path is unified, and Set-Cookie is explicitly forwarded to the client with SameSite and Secure cleared, which the comment says can be done with the ngx_headers_more module.

The static root is /var/dycast with a try_files rule that returns index.html for unmatched paths, which is the standard fix for a single page application on refresh, and an Access-Control-Allow-Origin header is added on the root location.

There is also a server.py at the root of the repository, though the documentation does not explain what it is for.

## A reconnect mechanism exists, but the platform can invalidate the whole approach

The feature list mentions a reconnection mechanism that guarantees connection stability to some degree. That is a reasonable engineering response to a live websocket that the platform may close, and the ws/wss address is computed per session rather than cached forever.

The larger problem is not the connection. It is that every part of the pipeline depends on undocumented platform behaviour. The signature is computed by reverse engineering, the proto files were reconstructed by watching what the platform parses, and the endpoint hostnames in the nginx configuration are hardcoded, including the difference between the webcast100 and webcast5 hosts used for the proxy and the Origin header respectively. Any of those can change without notice.

The gift danmaku category also requires a login cookie, which is a user account credential and a different risk profile from anonymous read access.

The project ships no licence statement. The licence is reported as unknown, the LICENSE file is not in the top level entry list, and the repository root contains a small set of files with no packaging metadata. That matters more than usual here, because the code includes reverse engineered protocol details and reconstructed proto files, and a team planning to run this internally should decide for itself what it is comfortable with.

The last push was on 2026-09-12 and the repository has no GitHub releases, so there is no version history to diff and the version field in the manifest, 1.1.0, is the only marker. A single maintainer project with no releases and an unknown licence is fine for a personal tool and uncomfortable for anything a company depends on.

## Where dycast is the wrong tool

The first wrong-tool case is any use that needs a supported contract. Douyin does not publish this protocol, so a change on the platform side breaks the tool without warning, and there is no vendor to file an issue with. A business that depends on danmaku capture cannot treat this as infrastructure, because it is a reimplementation of a private interface.

The second case is a project that only needs the raw data. dycast is a front end plus a proxy, with a browser interface aimed at a human watching a room. If your goal is to store danmaku in a database for analysis, you would be running a UI, an nginx proxy and a Vite build to get at a websocket that you then forward to a backend anyway. The forwarding address field exists, so you can use it that way, but you are carrying the whole front end to do it.

The third case is a deployment where you cannot run nginx or cannot make cross-origin requests to the platform. The architecture is browser plus reverse proxy, and the whole cookie and User-Agent rewriting exists because the browser cannot talk to Douyin directly. Behind a corporate proxy that terminates TLS, or in an environment where outbound connections to those hosts are blocked, the tool does not work.

The last point is about the project itself rather than the platform. With an unknown licence, no GitHub releases, and a single visible maintainer, the honest question is who maintains it if the platform changes. The answer in the documentation is a Discord community and a video demo on Bilibili, which tells you where the audience is but not who is on the hook for the reverse engineering.

## Conclusion

dycast suits a developer who wants a live danmaku feed out of a Douyin room for a bot, a dashboard or a data collection script, and it works as a static front end behind an nginx proxy. It does not suit a production service, because the whole approach depends on reverse engineered platform internals that Douyin can change at any time, and the project carries an unknown licence, so redistribution terms are unresolved. Before adopting, run npm install and npm run dev locally, confirm the signature computation in src/core/signature still produces a working wss link for a live room, and keep the nginx proxy configuration for the /dylive and /socket locations, since the front end depends on those proxies rather than talking to the platform directly from the browser.

## FAQ

### How do I run dycast locally?

Install the dependencies with npm install, start the development server with npm run dev, and produce a production bundle with npm run build. The project is a Vite and Vue application, and the manifest pins pnpm as the package manager with a pnpm-lock.yaml in the repository.

### How does dycast compute the websocket connection to a Douyin live room?

The signature parameter of the wss address is computed from the roomId and the uniqueId by a function packaged in src/core/signature, and the project describes that calculation as derived through reverse engineering. The signature is combined with roomId and uniqueId to build the full wss link.

### How does dycast parse the binary danmaku frames?

A websocket frame is parsed as a PushFrame, its payload is gzip compressed and is decompressed, the result is parsed as a Response, and the messages array holds Message structures whose payload is the specific danmaku body such as ChatMessage, MemberMessage or LikeMessage. The proto files are compiled to TypeScript with protobufjs, with Long values changed to strings.

### How do I forward the danmaku to my own backend with dycast?

Enter the WebSocket address of your own server in the forward address field on the right side of the interface and click forward, which establishes the connection and pushes the messages in real time. The forwarded payload is JSON matching the DyMessage interface, and the documentation says gift danmaku requires a login cookie.

### Why does dycast need an nginx proxy?

The browser cannot reach the Douyin live endpoints directly, so the documentation proxies two paths through nginx: the API path to live.douyin.com and the websocket path to the webcast host, rewriting Host, Referer, Origin and User-Agent headers and forwarding Set-Cookie so the platform sees a desktop browser.

## Sources

- [Issues](https://github.com/skmcj/dycast/issues)
- [README](https://github.com/skmcj/dycast/blob/main/README.md)
- [skmcj/dycast on GitHub](https://github.com/skmcj/dycast)

---

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