# zhihu/griffith: a React video player with MSE-based MP4 and HLS plugins

> Griffith is Zhihu's React video player, split into a core component plus optional griffith-mp4 and griffith-hls plugins. The README is explicit about one thing: SSR is not supported.

**zhihu/griffith** — A React-based web video player

- Repository: https://github.com/zhihu/griffith
- Website: https://codesandbox.io/embed/p03wm0o80
- Stars: 2,507 · Forks: 226
- Language: TypeScript
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/zhihu-griffith

## What problem Griffith solves, and for whom

The README frames Griffith around three claims: streaming, extensibility, and reliability. Of those, streaming is the one with a concrete mechanism behind it. The project notes that whether your video format is mp4 or hls, Griffith can use Media Source Extension (MSE) for segment loading. That matters because plain HTML5 video leaves format support to the browser, and browser support for fragmented MP4 and HLS is uneven. Griffith moves that work into JavaScript.

The audience is narrow and specific. You are building a React application, or a non-React page where you can load a script tag, and you want a player component rather than a full media framework. The repository topics list react, react-components, html5-video, mp4, fmp4, hls and mp4box, which is a fair summary of the surface area.

It is not a general-purpose media platform. There is no mention of DRM, advertising, analytics or server-side tooling in the README. The reliability claim rests on one sentence: Griffith has been widely used in the web and mobile web of Zhihu. That is a deployment statement, not a benchmark, and the README offers no numbers behind it.

## How the core, the MP4 plugin and the HLS plugin fit together

Griffith is a Monorepo managed with Yarn workspace and Lerna. The layout is the clearest documentation of the architecture. packages/griffith is the core library. packages/griffith-mp4 is an MP4 plugin powered by the MediaSource API. packages/griffith-hls is an HLS plugin powered by hls.js. Two utility packages sit alongside them: packages/griffith-message for cross-window message helpers and packages/griffith-utils for shared utilities.

The data flow implied by that split is straightforward. The React component owns the player UI and state. Format-specific work is delegated to a plugin, and the plugin talks to the browser through MSE rather than handing a URL straight to a video element. That is why the plugins are separable at build time: if you do not need one, you can remove it.

packages/griffith-standalone is the escape hatch for non-React consumers. It is described as a UMD build that can be used without React or Webpack, and the README's non-React example loads it from unpkg and calls Griffith.createPlayer(element).render({sources}). The same sources shape is used in both modes, which keeps the API consistent across the two entry points.

The example/ and website/ directories are workspaces in the root package.json, so the demos build from the same dependency graph as the library. The example uses Vite, judging by example/vite.config.js.

## Installing Griffith in a React app and rendering a first player

The README's React path starts with a package install. Griffith is published to npm as griffith, and the README uses Yarn:

```bash
yarn add griffith
```

After that, you import the default export and pass a sources object. Each key is a quality label and each value carries a play_url. The README's example uses hd and sd keys pointing at MP4 files hosted on Zhihu's static CDN:

```js
import Player from 'griffith'

const sources = {
  hd: {
    play_url: 'https://zhstatic.zhihu.com/cfe/griffith/zhihu2018_hd.mp4',
  },
  sd: {
    play_url: 'https://zhstatic.zhihu.com/cfe/griffith/zhihu2018_sd.mp4',
  },
}

render(<Player sources={sources} />)
```

What you should see is a player rendered into your React tree, with the two sources available as quality options. The README points to packages/griffith/README.md for detailed usage, so treat the snippet above as the minimum viable call rather than the full prop surface.

If your application is not based on React, the README gives a script-tag route through the standalone UMD build:

```html
<script src="https://unpkg.com/griffith-standalone/dist/index.umd.min.js"></script>
```

With that script loaded, the global Griffith object exposes createPlayer, which takes a DOM element and a render call carrying the same sources object as the React version. The README's note about SSR applies to both: Griffith is not supporting SSR application. That is stated as a flat constraint, not a caveat with a workaround attached.

## Shrinking the bundle by aliasing out griffith-mp4 and griffith-hls

The README treats build tools as including both plugins by default, and offers aliases as the way to cut bundle size. If you use webpack, the documented approach is resolve.alias. The README gives separate snippets for webpack v5 and v4, which differ in the replacement value:

```javascript
// webpack v5+
module.exports = {
  resolve: {
    alias: {
      'griffith-hls': false,
      'griffith-mp4': false,
    },
  },
}

// webpack v4
module.exports = {
  resolve: {
    alias: {
      'griffith-hls': 'griffith/null',
      'griffith-mp4': 'griffith/null',
    },
  },
}
```

The consequence is spelled out in the README: without griffith-mp4 or griffith-hls, Griffith can no longer play MP4 or HLS media unless the browser supports it natively. That sentence is the whole trade-off. You are not choosing between two equivalent paths. You are choosing between shipping the plugin and depending on the browser's own decoder for that format. On a modern desktop browser with a plain progressive MP4, dropping griffith-mp4 may cost you nothing. On a browser that needs MSE for the container you serve, it costs you playback.

Note that the README documents this alias technique for webpack specifically. It does not describe an equivalent recipe for Rollup or Vite, even though the repository carries a rollup.config.js and the example directory uses Vite. If you are on either of those, the alias approach is described only in webpack terms, and you will be translating it yourself.

## Where Griffith is the wrong choice

The SSR limitation is the first hard boundary. The README states it plainly and offers no mitigation. If your application renders on the server, the player component has no documented path there. This is not a small detail for teams running Next.js or similar frameworks, because it changes where the player can live in the component tree.

Release cadence is the second. The most recent tagged release listed is v1.5.0 from 2019-05-13, with v1.4.5 and v1.4.4 earlier that same year. The last push to the repository was on 2026-06-15, so the code has moved since those tags, but the published release history is old. Anyone who pins to a tagged npm version is pinning to something from 2019. Anyone who tracks master is tracking an untagged state. The README does not document a support policy or a compatibility matrix for either case.

The third boundary is scope. There is no mention of DRM, no mention of advertising or analytics integrations, and no server-side component. If your requirements include protected content, Griffith's documented feature set does not cover it, and you would be adding that layer yourself. The README also does not document rollback or migration procedures between versions, so upgrade planning has no documented reference point.

## Griffith against video.js and hls.js used directly

The most direct alternative is hls.js on its own. Griffith's HLS plugin is powered by hls.js, so the relationship is not competitive at the decoding layer. The difference is what sits above it. hls.js gives you an engine and an API; you build the controls, the quality selector and the React integration. Griffith gives you a React component with a sources object that already carries multiple quality levels, and the README's example shows hd and sd as keys in the same object. If you want the player chrome and the quality switching handled for you, Griffith is the shorter path. If you want to control the playback pipeline yourself, hls.js alone is more direct, and you avoid the React coupling entirely.

video.js is the other comparison worth drawing, and the contrast is architectural. video.js is built around a plugin and skin ecosystem with a long history of third-party extensions. Griffith's extension model is narrower: the README describes format support as separate workspace packages, griffith-mp4 and griffith-hls, that you can alias out at build time. That is a bundling decision, not a plugin marketplace. Griffith's other distinguishing feature is the UMD standalone build, which lets a non-React page call Griffith.createPlayer(element).render({sources}) without a React runtime. video.js is also usable outside React, but Griffith's pitch is specifically that the same sources shape works in both the React and standalone modes.

The honest summary: if you are already in React and want MP4 plus HLS with minimal wiring, Griffith's shape fits. If you need a large plugin ecosystem or a player that is not tied to React's rendering model, the alternatives are broader.

## Licence, maintenance and what an upgrade actually costs

Griffith is MIT licensed, stated in the README and in the root package.json. MIT is permissive: it allows commercial and closed-source use, and it requires that the copyright notice and permission notice be included in distributions. That is a description of the licence text, not legal advice. If your organisation has a policy review for third-party dependencies, the MIT identifier is the input you need, and the LICENSE file at the repository root is the authoritative copy.

On maintenance, the facts are limited to two data points. The repository is not archived, and the last push was on 2026-06-15. The newest tagged release, v1.5.0, is from 2019-05-13. Those two facts sit uneasily together: the code has seen activity, but the release channel has not produced a tag in years. For a consumer, that means the version you install from npm may not correspond to the state of master.

The upgrade cost follows from the monorepo structure. The root package.json defines workspaces for packages/*, example and website, and the build script runs lerna run build --scope 'griffith*'. If you vendor or fork Griffith, you inherit that toolchain: Yarn workspaces, Lerna, Rollup and a Jest test setup configured through jest.config.base.js and jest.config.js. The test script sets NODE_OPTIONS=--experimental-vm-modules, which tells you the test suite runs on an ESM-aware Jest configuration. None of that is unusual for a 2019-era TypeScript monorepo, but it is work you take on if you fork rather than depend.

The README does not document a deprecation path, a versioning policy, or migration notes between major versions. The CHANGELOG.md exists at the repository root, so that is where to look for change history rather than the README.

## Conclusion

Adopt Griffith if you are building a React (or plain browser) front end that needs MP4 and HLS playback through MSE and you are willing to own the dependency yourself, because the last push was on 2026-06-15 and the newest tagged release, v1.5.0, dates to 2019-05-13. Do not adopt it for a server-rendered app: the README states Griffith does not support SSR, and that constraint is not a configuration detail you can work around. Before wiring it in, verify two things in your own build: that your bundler resolves the griffith-mp4 and griffith-hls aliases the way you intend, and that the media your origin serves is actually reachable by the MSE path, since disabling either plugin leaves you with native browser playback only. If your player needs DRM, advertising integrations or a maintained release cadence, look elsewhere first.

## FAQ

### How do I use griffith in a React application?

Install it with yarn add griffith, then import the default export and render the Player component with a sources object. Each key in sources is a quality label such as hd or sd, and each value carries a play_url. The README points to packages/griffith/README.md for the detailed prop surface.

### Can I use griffith without React?

Yes. The README documents a standalone UMD build available at https://unpkg.com/griffith-standalone/dist/index.umd.min.js, which you load with a script tag. It exposes Griffith.createPlayer(element).render({sources}) using the same sources shape as the React version.

### Does griffith support server-side rendering?

No. The README states directly that Griffith is not supporting SSR application, and it offers no workaround or configuration for that case.

### How do I make the griffith bundle smaller?

The README says build tools include griffith-mp4 and griffith-hls by default and that you can exclude them with resolve.alias in webpack, using false for webpack v5 or 'griffith/null' for webpack v4. It warns that without those plugins Griffith can no longer play MP4 or HLS media unless the browser supports it natively.

### What licence does griffith use?

MIT. The README carries the licence badge and the root package.json declares "license": "MIT", with the LICENSE file at the repository root.

## Sources

- [License: MIT](https://github.com/zhihu/griffith/blob/master/LICENSE)
- [Project website](https://codesandbox.io/embed/p03wm0o80)
- [README](https://github.com/zhihu/griffith/blob/master/README.md)
- [Releases](https://github.com/zhihu/griffith/releases)
- [zhihu/griffith on GitHub](https://github.com/zhihu/griffith)

---

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