mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-08 16:11:05 +08:00
109 lines
2.8 KiB
JavaScript
109 lines
2.8 KiB
JavaScript
/**
|
|
* WebCodecs + MP4Box.js video compressor
|
|
* Simplified transcoding using browser WebCodecs API
|
|
*/
|
|
import { createFile } from 'mp4box'
|
|
import { UPLOAD_CONFIG } from '../config/uploadConfig.js'
|
|
|
|
export function isWebCodecSupported() {
|
|
return (
|
|
typeof window !== 'undefined' &&
|
|
'VideoEncoder' in window &&
|
|
'MediaStreamTrackProcessor' in window &&
|
|
'VideoFrame' in window
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Compress a video File using WebCodecs and MP4Box.js
|
|
* @param {File} file original video file
|
|
* @param {Object} options optional callbacks
|
|
* @param {Function} options.onProgress progress callback
|
|
* @returns {Promise<File>} compressed file
|
|
*/
|
|
export async function compressVideoWithWebCodecs(file, { onProgress = () => {} } = {}) {
|
|
if (!isWebCodecSupported()) {
|
|
throw new Error('当前浏览器不支持 WebCodecs')
|
|
}
|
|
|
|
onProgress({ stage: 'initializing', progress: 0 })
|
|
|
|
const url = URL.createObjectURL(file)
|
|
const video = document.createElement('video')
|
|
video.src = url
|
|
await video.play()
|
|
video.pause()
|
|
|
|
onProgress({ stage: 'preparing', progress: 10 })
|
|
|
|
const stream = video.captureStream()
|
|
const track = stream.getVideoTracks()[0]
|
|
const processor = new MediaStreamTrackProcessor({ track })
|
|
const reader = processor.readable.getReader()
|
|
|
|
const { width, height, frameRate = 30 } = track.getSettings()
|
|
const bitrate = UPLOAD_CONFIG.VIDEO.TARGET_BITRATE || 1_000_000
|
|
|
|
const chunks = []
|
|
const encoder = new VideoEncoder({
|
|
output: (chunk) => {
|
|
const copy = new Uint8Array(chunk.byteLength)
|
|
chunk.copyTo(copy)
|
|
chunks.push({ type: chunk.type, timestamp: chunk.timestamp, data: copy })
|
|
},
|
|
error: (e) => console.error('编码失败', e),
|
|
})
|
|
|
|
encoder.configure({
|
|
codec: 'avc1.42001E',
|
|
width,
|
|
height,
|
|
bitrate,
|
|
framerate: frameRate,
|
|
})
|
|
|
|
let processed = 0
|
|
const totalFrames = Math.ceil(video.duration * frameRate)
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
encoder.encode(value)
|
|
value.close()
|
|
processed++
|
|
onProgress({ stage: 'compressing', progress: Math.round((processed / totalFrames) * 80) })
|
|
}
|
|
|
|
await encoder.flush()
|
|
|
|
onProgress({ stage: 'packaging', progress: 90 })
|
|
|
|
const mp4 = createFile()
|
|
const trackId = mp4.addTrack({
|
|
id: 1,
|
|
type: 'avc1',
|
|
width,
|
|
height,
|
|
timescale: frameRate,
|
|
})
|
|
|
|
chunks.forEach((chunk) => {
|
|
mp4.addSample(trackId, chunk.data.buffer, {
|
|
duration: 1,
|
|
dts: chunk.timestamp,
|
|
cts: 0,
|
|
is_sync: chunk.type === 'key',
|
|
})
|
|
})
|
|
|
|
const streamOut = mp4.getBuffer()
|
|
const outBuffer = streamOut.buffer.slice(0, streamOut.position)
|
|
const outFile = new File([outBuffer], file.name.replace(/\.[^.]+$/, '.mp4'), {
|
|
type: 'video/mp4',
|
|
})
|
|
|
|
onProgress({ stage: 'completed', progress: 100 })
|
|
|
|
return outFile
|
|
}
|