Skip to content

Commit

Permalink
feat: modern-mp4
Browse files Browse the repository at this point in the history
  • Loading branch information
qq15725 committed Oct 17, 2023
1 parent c531e51 commit af15c7d
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 161 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@

```shell
npm i dom-vcr

# optional deps
npm i modern-gif # by export GIF
npm i modern-mp4 # by export MP4
npm i mp4box
```

<details>
Expand Down Expand Up @@ -66,16 +71,14 @@ generate()
<details>
<summary>MP4</summary><br>

> Need install `mp4box`
> Need install `mp4box``modern-mp4`
```ts
import { createVcr } from 'dom-vcr'
import mp4box from 'mp4box'

const dom = document.querySelector('#app')
const vcr = createVcr(dom, {
type: 'mp4',
mp4: mp4box,
interval: 1000,
})

Expand Down Expand Up @@ -103,12 +106,10 @@ generate()
```ts
import { createVcr } from 'dom-vcr'
import * as gif from 'modern-gif'

const dom = document.querySelector('#app')
const vcr = createVcr(dom, {
type: 'gif',
gif,
interval: 1000,
})

Expand Down
11 changes: 6 additions & 5 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@

```shell
npm i dom-vcr

# 可选依赖
npm i modern-gif # 导出 GIF 需要
npm i modern-mp4 # 导出 MP4 需要
npm i mp4box
```

<details>
Expand Down Expand Up @@ -66,16 +71,14 @@ generate()
<details>
<summary>MP4</summary><br>

> 需要安装 `mp4box`
> 需要安装 `mp4box``modern-mp4`
```ts
import { createVcr } from 'dom-vcr'
import mp4box from 'mp4box'

const dom = document.querySelector('#app')
const vcr = createVcr(dom, {
type: 'mp4',
mp4: mp4box,
interval: 1000,
})

Expand Down Expand Up @@ -103,12 +106,10 @@ generate()
```ts
import { createVcr } from 'dom-vcr'
import * as gif from 'modern-gif'

const dom = document.querySelector('#app')
const vcr = createVcr(dom, {
type: 'gif',
gif,
interval: 1000,
})

Expand Down
8 changes: 1 addition & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,19 @@
<button style="margin-top: 14px;" id="generateVideoByRecord">Generate Video(record 2s)</button>

<script type="module" async>
import * as gif from 'modern-gif'
import mp4 from 'mp4box'
import { createVcr } from './src'

const dom = document.querySelector('#app')

const videoVcr = await createVcr(dom, {
debug: true,
scale: 2,
type: 'mp4',
mp4,
interval: 800,
interval: 500,
})

const gifVcr = await createVcr(dom, {
debug: true,
scale: 2,
type: 'gif',
gif,
interval: 500,
})

Expand Down
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,29 @@
"conventional-changelog-cli": "^2.2.2",
"eslint": "^8.34.0",
"modern-gif": "^1.0.1",
"modern-mp4": "^0.0.3",
"mp4box": "^0.5.2",
"typescript": "^4.9.5",
"vite": "^4.1.2",
"vitest": "^0.28.5"
},
"dependencies": {
"modern-screenshot": "^4.4.20"
},
"peerDependencies": {
"modern-gif": "^1.*",
"modern-mp4": "^0.*",
"mp4box": "^0.*"
},
"peerDependenciesMeta": {
"modern-gif": {
"optional": true
},
"modern-mp4": {
"optional": true
},
"mp4box": {
"optional": true
}
}
}
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 4 additions & 18 deletions src/recorders/gif.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,20 @@
import { createEncoder } from 'modern-gif'
import type { Options, Recorder } from '../types'
import type { createEncoder } from 'modern-gif'

export function createGifRecorder(options: Options): Recorder {
const {
width,
height,
gif,
gifWorkerUrl,
gifWorkerNumber,
gifDebug,
gifMaxColors,
interval,
} = options

const encoder = gif?.createEncoder
? (gif.createEncoder as typeof createEncoder)({
width,
height,
workerUrl: gifWorkerUrl,
workerNumber: gifWorkerNumber,
debug: gifDebug,
maxColors: gifMaxColors,
})
: undefined
const encoder = createEncoder({ width, height })

return {
isSupported() {
return Boolean(encoder)
},
async addFrame(canvas) {
async addFrame(canvas, options) {
if (!encoder) return

const imageData = canvas.getContext('2d')
Expand All @@ -39,7 +25,7 @@ export function createGifRecorder(options: Options): Recorder {

await encoder.encode({
imageData,
delay: interval,
delay: options?.duration ?? interval,
})
},
async render() {
Expand Down
43 changes: 23 additions & 20 deletions src/recorders/mp4.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
import { SUPPORT_VIDEO_ENCODER } from '../utils'
import { createVideoEncoder } from '../video-encoder'
import { Encoder, SUPPORTS_VIDEO_ENCODER } from 'modern-mp4'
import type { Options, Recorder } from '../types'

export function createMp4Recorder(options: Options): Recorder {
const { width, height, interval, mp4 } = options
const { width, height, interval, mp4Options } = options

const config: VideoEncoderConfig = {
width,
height,
framerate: 1000 / interval,
codec: 'avc1.42E01F',
hardwareAcceleration: 'prefer-hardware',
bitrate: 6_000_000,
avc: { format: 'avc' },
let encoder: Encoder | undefined
if (SUPPORTS_VIDEO_ENCODER) {
encoder = new Encoder({
width,
height,
fps: Math.floor(1000 / interval),
audio: false,
...mp4Options,
})
}

let encoder: ReturnType<typeof createVideoEncoder> | undefined

if (SUPPORT_VIDEO_ENCODER && mp4) {
encoder = createVideoEncoder(mp4)
encoder.configure(config)
}
let timestamp = 1

return {
async isSupported() {
try {
return Boolean(encoder) && Boolean((await VideoEncoder.isConfigSupported(config)).supported)
return Boolean(await encoder?.isConfigSupported())
} catch (error) {
return false
}
},
async addFrame(canvas) {
encoder?.encode(await createImageBitmap(canvas))
async addFrame(canvas, options) {
const duration = (options?.duration ?? interval) * 1000

encoder?.encode(canvas, {
timestamp,
duration,
})

timestamp += duration
},
async render() {
if (!encoder) {
throw new Error('MP4 encoder is a must')
}
timestamp = 1
return new Blob([await encoder.flush()], { type: 'video/mp4' })
},
}
Expand Down
20 changes: 10 additions & 10 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { EncoderOptions as GifEncoderOptions } from 'modern-gif'

Check failure on line 1 in src/types.ts

View workflow job for this annotation

GitHub Actions / Lint: node-18, ubuntu-latest

All imports in the declaration are only used as types. Use `import type`
import { EncoderOptions as Mp4EncoderOptions } from 'modern-mp4'

Check failure on line 2 in src/types.ts

View workflow job for this annotation

GitHub Actions / Lint: node-18, ubuntu-latest

All imports in the declaration are only used as types. Use `import type`

export type GifOptions = Exclude<GifEncoderOptions, 'width' | 'height'>
export type Mp4Options = Exclude<Mp4EncoderOptions, 'width' | 'height'>

export interface Options {
width: number
height: number
interval: number
type: 'mp4' | 'gif' | 'webm'
// modern-gif
gif?: any
gifWorkerUrl?: string
gifWorkerNumber?: number
gifDebug?: boolean
gifMaxColors?: number
// mp4box
mp4?: any
gifOptions?: GifOptions // modern-gif
mp4Options?: Mp4Options // modern-mp4
}

export interface Mp4FrameOptions {
//
duration?: number
}

export interface GifFrameOptions {
delay?: number
duration?: number
}

export type FrameOptions = Mp4FrameOptions | GifFrameOptions
Expand Down
1 change: 0 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Constants
export const IN_BROWSER = typeof window !== 'undefined'
export const SUPPORT_VIDEO_ENCODER = IN_BROWSER && 'VideoEncoder' in window
export const SUPPORT_MEDIA_RECORDER = IN_BROWSER && 'MediaRecorder' in window

export const isElementNode = (node: Node): node is Element => node.nodeType === 1 // Node.ELEMENT_NODE
Loading

0 comments on commit af15c7d

Please sign in to comment.