Library / SDK
goldvideo/h265player avatar
goldvideo/h265player

h265player decodes HEVC in WebAssembly because browsers still will not do it natively

一套完整的Web版H.265播放器解决方案,非常适合学习交流和实际应用。基于JS码流解封装、WebAssembly(FFmpeg)视频解码,利用Canvas画布投影、AudioContext播放音频。

1,659 stars301 forksJavaScriptLicense varies

At a glance

What is it?
goldvideo's h265player is a browser player that works around the missing H.265 decode path by demuxing HLS in JavaScript, decoding HEVC with FFmpeg compiled to WebAssembly, painting YUV frames to a canvas and decoding AAC through the Web Audio API, with audio as the clock for A/V sync. It suits a project that must play H.265 today on Chrome, Safari and Firefox, and it accepts the CPU cost of software decoding as the price.
Who is it for?
Adopt h265player if your content is H.265 and your users are on browsers that do not decode it natively, since the pipeline of JavaScript demuxing, FFmpeg WebAssembly decode and YUV canvas rendering is a working answer today rather than a roadmap.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why the whole pipeline exists at all

The premise is stated plainly in the README: compared with H.264, H.265 gives comparable quality at roughly half the file size and half the bandwidth, with finer picture quality. But web browsers still do not support H.265 decoding, so this project assembles the missing half itself.

Four pieces do the work. JavaScript demuxes the container and separates video and audio. FFmpeg compiled to WebAssembly does the software decode. A canvas receives the decoded frames. The Web Audio API plays the audio. The project describes itself as a complete web H.265 player solution and explicitly as a public, basically usable version with no business logic, meaning the player is meant to be embedded in a product rather than adopted whole.

Supported browsers are listed with floors: Chrome above 57, Safari above 11, Firefox above 52. Those numbers matter because the WebAssembly and Web Audio APIs the design depends on were still settling at the time, and the floors are where the authors found it worked.

The repository is organised as four sibling projects under a goldvideo directory: demuxer, h265player, decoder_wasm and example. The player consumes the other three rather than containing them.

Three threads, and where the CPU goes

The architecture section divides the player into UI, Loader, data processing and data rendering, spread over three threads.

The main thread handles interface control, download control, stream control and audio-video control. A loader thread requests metadata and data segments. A data processing thread does demuxing and decoding. The README is direct about the cost: H.265 decoding is done by an FFmpeg-built WASM software decoder, and the CPU usage rate is high.

That sentence is the single most important engineering fact in the document. Everything else in the design is a way of keeping that cost from also being a latency problem, and it explains why the player manages several buffers and why sync is handled by adjusting video rather than audio.

The loader has its own back-pressure mechanism. It only supports the HLS protocol, and it is implemented with a worker. Once data is loaded it stores the requested ts data according to a configured cache size and stops loading when the cache ceiling is reached. When the decoder pulls ts data off the queue, the HLS loader releases the already-requested ts and continues loading the next one, and loading stops again at the maximum cache limit. That is a bounded buffer with a consumer signal, not an unbounded prefetch, and it is the reason memory stays flat during playback.

Audio is the clock, video is corrected against it

The sync strategy is stated exactly: audio is the reference, and the difference between the current video pts and the obtained audio pts is used to adjust video display time.

That choice follows from the CPU situation. Audio runs through the Web Audio API, which is the browser's own code, and it plays on the browser's own clock. Video frames come from a software decoder in a worker, so their timing is whatever the machine managed. Adjusting video against audio means you always nudge the side that is under your control.

The audio path has three components. The Audio Player receives AAC data from the demuxer and decodes it to PCM through the AudioContext decodeAudioData API. The audio nodes in the chain are processors that handle audio data serially before it reaches audioContext.destination: a ScriptProcessorNode as the audio source, chosen over options such as AudioBufferSourceNode because of the need for variable speed playback, requesting data through the onaudioprocess callback, and a GainNode for volume control.

For speed change without pitch change, the project uses the SoundTouchJS library. The README explains why a library is needed: the playbackRate property on audio nodes changes speed and pitch together, raising or lowering the tone, so processed audio is fed to the ScriptProcessorNode instead to keep the pitch constant.

Video path: decoded YUV straight onto a canvas

Rendering reuses an existing library rather than implementing colour conversion itself. The ImagePlayer renders YUV data through yuv-canvas, and it is that dependency which turns the decoder's raw output into pixels on screen.

package.json confirms the shape of the pipeline at the dependency level. yuv-canvas is at 1.2.6 and yuv-buffer at 1.0.0, so frames are held as YUV buffers before being converted. The demuxer dependency is the separate demuxer package at 2.1.2, mp4box is listed at 2.3.0 and loadjs at 4.2.0.

The ImagePlayer holds a queue of YUV data and computes the time length of the current YUV data. When that reaches the readybufferLength value it fires the ImagePlayerReady event. Only when both the audio and video players are ready does the player fire dataReady and begin calling play.

That gating is worth noting. It means the first frame is not shown until enough decoded video and decoded audio exist, which is why the player has a buffering phase at start that a naive implementation would skip.

There is an MP4 route as well as HLS: the repository has MP4-USAGE.md, a debug-mp4.html demo and mp4box in the dependency list, but the Loader section says HLS is the only protocol currently supported for media loading, so treat MP4 support as separate from the streaming path.

Building it from source

The clone and install steps are given directly:

shell
* mkdir goldvideo
* cd goldvideo
* git clone https://github.com/goldvideo/h265player.git
* cd h265player
* npm install

The README also notes npm install --legacy-peer-deps as an alternative when peer dependency conflicts get in the way, which is a hint that the dependency tree is older than the current npm default.

There are four build commands, described as mutually interchangeable choices:

shell
* npm run dev    # 运行开发环境
* npm run test   # 运行测试环境
* npm run build  # 打包正式环境
* rollup -c      # 打包csj与esm版本

rollup -c produces the CommonJS and ESM builds, which matches the two demo files demo-cjs.html and demo-esm.html. In package.json the scripts point at webpack configuration files under build/: webpack.dev.conf.js with watch, webpack.prod.conf.js, and webpack.test.conf.js for the test environment. Documentation is generated with jsdoc -c jsdoc.json.

Serving the demo needs a static server, and the README supplies an nginx server block:

javascript
server {
    listen       8000;
    location / {
        root   <path of goldvideo>/goldvideo;
        index  index.html index.htm;
        autoindex on;
    }
}

With nginx restarted, the test page is at http://localhost:8000/h265player/demo/demo.html, and if video plays you know the build is wired correctly. NGINX_CONFIG.md at the repository root holds the same information, and server.js and server.py are also present if you would rather not configure nginx.

Wiring it into a page

The integration surface is small. Two assets and a container div, with a style block setting the container size:

html
<link rel="stylesheet" href="../dist/goldplay-h265.css">
<script src="../dist/goldplay-h265-sdk.js"></script>
<style>
    .play-container {
        width: 800px;
        height: 500px;
    }
</style>

Then a container div with class play-container, and the constructor:

javascript
let el = doc.querySelector('.play-container')
let options = {
    // 视频播放地址
    sourceURL: 'http://localhost:8000/h265player/data/video2/playlist.m3u8',
    type: 'HLS'
    // wasm库地址
    libPath: 'http://localhost:8000/h265player/dist/lib',
}
let player = new GoldPlay(el, options}

Three things to note. sourceURL is an m3u8 playlist and type is HLS, so the Loader requirement is unavoidable. libPath points at the directory holding libffmpeg.js and libffmpeg.wasm, which means the WebAssembly binary is fetched at runtime from wherever you host it, and that path has to be right or nothing plays. And the snippet as printed has a missing comma after type: 'HLS' and a closing brace on the constructor line, so fix both before you paste it.

The published package route avoids the build. npm i goldvideo-player installs the built files, and a separate goldvideo/example repository has runnable demos.

For the UI itself, extending it is documented in docs/addComponentReadme.md with a demo at demo/componentadd.html, and the API reference is published separately.

Where this is the wrong choice

Be clear about the limits.

The decoder is software. A WASM build of FFmpeg is not hardware decode, so CPU cost scales with resolution and frame rate, and a 1080p stream on a low-end laptop or phone is the case that will hurt. The README does not publish performance numbers, so measure your own content before committing.

The Loader only supports HLS. Progressive MP4 is a separate path with its own documentation file and its own demo pages, and the streaming loader will not fetch it.

Sync depends on audio. If a stream has no AAC track, or if audio is broken, the reference clock the video is corrected against is missing. That is a content requirement, not an option.

The WASM binary is a prebuilt artifact. decoder_wasm has already been compiled and copied into h265player/dist/lib/, and you can rebuild it yourself from that repository, but the README notes the WASM installation is somewhat complicated and defers to its own documentation. Replacing libffmpeg.js and libffmpeg.wasm is therefore a supported path with a real learning curve.

On maintenance and terms: the last push was on 2026-04-12 and the listed release is version 1.1, dated 2026-04-03, while package.json reads version 1.0.2 for the npm package goldvideo-player. No licence is identified in the repository metadata, so before you ship this in a product, establish the terms with the authors directly. Support runs through a Slack group and a WeChat group rather than an issue tracker.

Editorial conclusion

Adopt h265player if your content is H.265 and your users are on browsers that do not decode it natively, since the pipeline of JavaScript demuxing, FFmpeg WebAssembly decode and YUV canvas rendering is a working answer today rather than a roadmap. Do not adopt it for MP4 or H.264 content, where the native element already works and this player adds a software decoder for nothing, and do not adopt it for long-form video on low-end hardware because the README states outright that the software decode path has high CPU usage. Check first that your HLS segments carry AAC audio, since the sync strategy takes audio as the reference clock, and read the loader section carefully because it only supports the HLS protocol and stops requesting once its cache limit is reached.

Frequently asked questions

How do I install and run the h265player demo?

Clone https://github.com/goldvideo/h265player.git, run npm install in the h265player directory, build with npm run dev, npm run test, npm run build or rollup -c, then serve the goldvideo directory with a static server such as nginx and open http://localhost:8000/h265player/demo/demo.html.

Which browsers can play H.265 with h265player?

The README lists Chrome above 57, Safari above 11 and Firefox above 52. Playback works without native H.265 support because the video is demuxed in JavaScript and decoded by FFmpeg compiled to WebAssembly.

How does h265player keep audio and video in sync?

Audio is the reference. The player compares the current video pts with the audio pts it has obtained and adjusts video display time from the difference, so the side under its own control is the one being corrected.

Can I use h265player without building it from source?

Yes. The built files are published to npm as goldvideo-player and install with npm i goldvideo-player, with runnable examples in the separate goldvideo/example repository.

Does h265player support speed changes without changing pitch?

Yes. Setting playbackRate on the Web Audio nodes changes pitch along with speed, so the project uses the SoundTouchJS library to produce time-stretched audio with unchanged pitch and feeds that into the ScriptProcessorNode.

Official sources

  1. goldvideo/h265player on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/goldvideo-h265player.svg)](https://hysenlabs.com/projects/goldvideo-h265player)

Was this useful?