├── ffmpeg ├── .gitkeep └── build-ffmpeg.sh ├── .gitignore ├── .prettierrc.js ├── wasm ├── libav.h ├── CMakeLists.txt ├── main.cpp ├── AVVideoFile.hpp └── AVAudioFile.hpp ├── full-build.sh ├── package.json ├── src ├── renderers │ ├── canvas.ts │ └── webgl.ts ├── wasm-module-types.ts ├── index.html └── index.ts ├── README.md └── .github └── workflows └── build-and-deploy.yml /ffmpeg/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ffmpeg/src 2 | ffmpeg/built 3 | wasm/build 4 | node_modules 5 | dist 6 | .cache 7 | .vscode -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require("@rinsuki/prettier-config"), 3 | printWidth: 120, 4 | } -------------------------------------------------------------------------------- /wasm/libav.h: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include 4 | #include 5 | #include 6 | #include 7 | } -------------------------------------------------------------------------------- /full-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -xe 3 | 4 | cd ffmpeg 5 | ./build-ffmpeg.sh 6 | 7 | cd ../wasm 8 | mkdir -p build 9 | cd build 10 | emcmake cmake .. 11 | emmake make 12 | 13 | cd ../.. 14 | yarn parcel build ./src/index.html -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "play-flv-in-browser", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "eslintConfig": { 6 | "extends": "@rinsuki" 7 | }, 8 | "devDependencies": { 9 | "@rinsuki/eslint-config": "^1.0.0", 10 | "@rinsuki/prettier-config": "^1.0.0", 11 | "@types/emscripten": "^1.39.3", 12 | "eslint": "^6.8.0", 13 | "parcel-bundler": "^1.12.4", 14 | "prettier": "^2.0.5", 15 | "typescript": "^3.8.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/renderers/canvas.ts: -------------------------------------------------------------------------------- 1 | export class CanvasRenderer { 2 | ctx: CanvasRenderingContext2D 3 | imageData: ImageData 4 | 5 | constructor(public canvas: HTMLCanvasElement) { 6 | console.warn("Using CanvasRenderer") 7 | const { width, height } = canvas 8 | this.ctx = canvas.getContext("2d") 9 | this.imageData = this.ctx.createImageData(width, height) 10 | } 11 | 12 | render(videoFile: { convertFrameToRGB(): Uint8Array }) { 13 | const received: Uint8Array = videoFile.convertFrameToRGB() 14 | this.imageData.data.set(received) 15 | this.ctx.putImageData(this.imageData, 0, 0) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ffmpeg/build-ffmpeg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | git clone -b n4.2.1 https://github.com/ffmpeg/ffmpeg src || true 5 | cd src 6 | 7 | emconfigure ./configure \ 8 | --cc=emcc --ar=emar --ranlib=emranlib --prefix=$(pwd)/../built --enable-cross-compile --target-os=none --arch=x86_32 --cpu=generic \ 9 | --disable-stripping --enable-shared --disable-programs --disable-asm --disable-doc --disable-devices --disable-pthreads --disable-w32threads --disable-network --disable-debug --disable-xlib --disable-zlib --disable-sdl2 --disable-iconv --disable-everything --enable-protocol=file \ 10 | --enable-decoder=vp6f --enable-decoder=flv --enable-decoder=h264 \ 11 | --enable-decoder=mp3 --enable-decoder=aac \ 12 | --enable-demuxer=flv --enable-demuxer=mov \ 13 | --enable-muxer=mp3 --enable-muxer=mp4 14 | 15 | emmake make -j 16 | emmake make install -------------------------------------------------------------------------------- /wasm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(PlayFLVInBrowser) 3 | 4 | set(CMAKE_C_FLAGS "-s MODULARIZE=1 -s FORCE_FILESYSTEM=1 --bind -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ALLOC_DYNAMIC\", \"allocate\", \"FS\"]' -s INITIAL_MEMORY=268435456") 5 | set(CMAKE_CXX_FLAGS "-s MODULARIZE=1 -s FORCE_FILESYSTEM=1 --bind -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ALLOC_DYNAMIC\", \"allocate\", \"FS\"]' -s INITIAL_MEMORY=268435456") 6 | 7 | # Load FFMPEG 8 | get_filename_component(FFMPEG_PKGCONFIG_PATH "../ffmpeg/built/lib/pkgconfig" ABSOLUTE) 9 | set(ENV{PKG_CONFIG_PATH} "${FFMPEG_PKGCONFIG_PATH}") 10 | message("$ENV{PKG_CONFIG_PATH}") 11 | include(FindPkgConfig) 12 | pkg_check_modules(FFMPEG REQUIRED libavformat libavcodec libavutil libswscale libswresample) 13 | include_directories(BEFORE SYSTEM ${FFMPEG_INCLUDE_DIRS}) 14 | link_directories(${FFMPEG_LIBRARY_DIRS}) 15 | link_libraries(${FFMPEG_LIBRARIES}) 16 | 17 | set(SOURCE_FILES main.cpp) 18 | add_executable(wasm-module ${SOURCE_FILES}) -------------------------------------------------------------------------------- /src/wasm-module-types.ts: -------------------------------------------------------------------------------- 1 | import wasm from "../wasm/build/wasm-module.js" 2 | 3 | // TODO: generate directly from AVVideoFile.hpp or main.cpp 4 | declare class AVAudioFile { 5 | constructor(filePath: string) 6 | output(): void 7 | path(): string 8 | readonly isFailed: boolean 9 | } 10 | 11 | declare class AVVideoFile { 12 | constructor(filePath: string) 13 | readFrame(): number 14 | isVideoStream(): boolean 15 | packetUnref(): void 16 | sendPacket(): number 17 | receiveFrame(): number 18 | width(): number 19 | height(): number 20 | getPixFmt(): void 21 | convertFrameToRGB(): Uint8Array 22 | outputArray(index: 0 | 1 | 2): Uint8Array 23 | pts(): number 24 | timeBase(): [number, number] 25 | seekToFirst(): number 26 | readonly isFailed: boolean 27 | } 28 | 29 | declare interface WASMModule extends EmscriptenModule { 30 | FS: typeof FS 31 | AVAudioFile: typeof AVAudioFile 32 | AVVideoFile: typeof AVVideoFile 33 | } 34 | 35 | export default wasm as WASMModule 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Play FLV in Browser 2 | 3 | DEMO → https://play-flv-in-browser.netlify.app/ 4 | 5 | Play FLV in Browser (without Flash Plugin!) 6 | 7 | ## Supported Codecs 8 | 9 | - FLV1 10 | - VP6 11 | - H.264 12 | - MP3 13 | - AAC 14 | 15 | see `ffmpeg/build_ffmpeg.sh` . 16 | 17 | ## 解説 18 | 19 | 上記コーデックのdecoder、FLV/MP4のdemuxer、MP3/MP4のmuxer(音声用) を入れた FFmpeg を WebAssembly にコンパイルし、JavaScriptから呼び出した後、結果をcanvasに描画しています。 20 | また、WebAssembly と JavaScript の世界の行き来は遅いので、なるべく行き来する回数を減らすために `wasm/*.{cpp,hpp}` でWebAssembly内にFFmpegのラッパーを作り、JavaScriptからWebAssemblyの世界を呼び出す回数をなるべく減らしています。 21 | 22 | 音声は FFmpeg で音声トラックだけ remux し、mp3もしくはmp4にしてからブラウザのネイティブ`