create-react-app zhufengbinary
cd zhufengbinary
yarn start
byte array
ArrayBuffer
的内容,而是要通过类型数组对象
或 DataView
对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。//创建一个长度为8个字节的buffer
const buffer = new ArrayBuffer(8);
console.log(buffer.byteLength);
TypedArray对象描述了一个底层的二进制数据缓冲区(binary data buffer)的一个类数组视图(view)
类型 | 单个元素值的范围 | 大小(bytes) | 描述 |
---|---|---|---|
Int8Array | -128 to 127 | 1 | 8 位二进制有符号整数 |
Uint8Array | 0 to 255 | 1 | 8 位无符号整数 |
Int16Array | -32768 to 32767 | 2 | 16 位二进制有符号整数 |
Uint16Array | 0 to 65535 | 2 | 16 位无符号整数 |
const buffer = new ArrayBuffer(8);
console.log(buffer.byteLength);//8
const int8Array = new Int8Array(buffer);
console.log(int8Array.length);//8
const int16Array = new Int16Array(buffer);
console.log(int16Array.length);//4
setInt8()
从DataView起始位置以byte为计数的指定偏移量(byteOffset)处储存一个8-bit数(一个字节)getInt8()
从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个8-bit数(一个字节)new DataView(buffer [, byteOffset [, byteLength]])
const buffer = new ArrayBuffer(8);
console.log(buffer.byteLength);//8个字节
const view1 = new DataView(buffer);
view1.setInt8(0, 1);
console.log(view1.getInt8(0));//1
view1.setInt8(1, 2);
console.log(view1.getInt8(1));//2
console.log(view1.getInt16(0));//258
readAsText()
:读取文本文件(可以使用txt打开的文件),返回文本字符串,默认编码是UTF-8readAsDataURL()
:读取文件获取一段以data开头的字符串,这段字符串的本质就是DataURL,DataURL是一种将文件嵌入到文档的方案。DataURL是将资源转换为base64编码的字符串形式,并且将这些内容直接储存在url中var aBlob = new Blob( array, options );
ArrayBuffer
, ArrayBufferView
, Blob
, DOMString
等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings会被编码为UTF-8。BlobPropertyBag
字典type
默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型<body>
<script>
let debug = { name: "zhufeng" };
let str = JSON.stringify(debug);
console.log("str", str);
var blob = new Blob([str], { type: "application/json" });
console.log("blob.size", blob.size);
function readBlob(blob, type) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = function (event) {
resolve(event.target.result);
};
switch (type) {
case "ArrayBuffer":
reader.readAsArrayBuffer(blob);
break;
case "DataURL":
reader.readAsDataURL(blob);
break;
case "Text":
reader.readAsText(blob,'UTF-8');
break;
default:
break;
}
});
}
readBlob(blob, "ArrayBuffer").then((buffer) => {
console.log("buffer", buffer);
});
readBlob(blob, "DataURL").then((base64String) => {
console.log("base64String", base64String);
});
readBlob(blob, "Text").then((text) => {
console.log("text", text);
});
</script>
</body>
Blob
数据blob:<origin>/<uuid>
URL.createObjectURL()
创建的 URL 对象<body>
<button onclick="download()">下载json</button>
<script>
function download () {
let debug = { hello: "world" };
let str = JSON.stringify(debug);
var blob = new Blob([str], { type: 'application/json' });
let objectURL = URL.createObjectURL(blob);
const a = document.createElement('a');
a.download = 'hello.json';
a.rel = 'noopener';
a.href = objectURL;
a.dispatchEvent(new MouseEvent('click'));
URL.revokeObjectURL(objectURL);
}
</script>
</body>
import React from 'react';
import ReactDOM from 'react-dom';
import Image from './Image';
ReactDOM.render(
<Image />,
document.getElementById('root')
);
src\Image.js
import React from 'react';
export default class Image extends React.Component{
imageRef = React.createRef() //图片
canvasRef = React.createRef() //完整的canvas
avatarRef = React.createRef() //裁剪后的头像
state = {
file:null,//添加的文件
dataURL:'',//转换成的base64字符串
times:1,//图片放大的倍数
lastLeft:0,//最后一个左边的距离
lastTop:0,//最后一个上面的距离
avatarDataURL:''//头像的base64字符串
}
handleChange = (event)=>{//头像选择改变
let file = event.target.files[0];
let fileReader = new FileReader();//读取文件
fileReader.onload = (event)=> {
this.setState({file,dataURL:event.target.result});
this.imageRef.current.onload = ()=>this.draw();//绘制到canvas上
};
fileReader.readAsDataURL(file);
}
draw=(left=this.state.lastLeft,top=this.state.lastTop)=>{
let image = this.imageRef.current;//图像
let canvas = this.canvasRef.current;//canvas
const ctx = canvas.getContext("2d");
ctx.clearRect(0,0,canvas.width, canvas.height);//清掉老图片
let imageWidth = image.width;//图片宽度
let imageHeight = image.height;//图片高度
if(imageWidth>imageHeight){//如果宽比高度大
let times = canvas.width/imageWidth;
imageWidth=canvas.width*this.state.times;//让宽度等于canvas宽度
imageHeight=times*imageHeight*this.state.times;//然后让高度等比缩放
}else{
let times = canvas.height/imageHeight;
imageHeight=canvas.height*this.state.times;
imageWidth=times*imageWidth*this.state.times;
}
ctx.drawImage(image, (canvas.width-imageWidth)/2+left, (canvas.height-imageHeight)/2+top,imageWidth,imageHeight);
}
bigger = ()=>{
this.setState({times:this.state.times+0.1},()=>this.draw());
}
smaller = ()=>{
this.setState({times:this.state.times-0.1},()=>this.draw());
}
confirm = ()=>{
let canvas = this.canvasRef.current;//头像canvas
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(100, 100, 100, 100);//获取头像数据
let clipCanvas = document.createElement('canvas');
clipCanvas.width=100;
clipCanvas.height=100;
const clipContext = clipCanvas.getContext("2d");
clipContext.putImageData(imageData, 0, 0);
let dataUrl = clipCanvas.toDataURL();
this.avatarRef.current.src = dataUrl;
this.setState({avatarDataURL:dataUrl});
}
handleMouseDown = (event)=>{
this.setState({startX:event.clientX,startY:event.clientY,startDrag:true});
}
handleMouseMove = (event)=>{
if(this.state.startDrag)
this.draw((event.clientX - this.state.startX)+this.state.lastLeft,(event.clientY - this.state.startY)+this.state.lastTop);
}
handleMouseUp = (event)=>{
this.setState({lastLeft:(event.clientX - this.state.startX)+this.state.lastLeft,lastTop:(event.clientY - this.state.startY)+this.state.lastTop,startDrag:false});
}
upload = ()=>{
let bytes = atob(this.state.avatarDataURL.split(",")[1]);
let arrayBuffer = new ArrayBuffer(bytes.length);
let uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < bytes.length; i++) {
uint8Array[i] = bytes.charCodeAt(i);
}
let blob = new Blob([arrayBuffer], { type: 'image/png' });
let request = new XMLHttpRequest();
let formData = new FormData();
formData.append("name", "zhufeng");
formData.append("avatar", blob);
request.open("POST", 'http://localhost:8080/upload', true);
request.send(formData);
}
render(){
return (
<div className="container">
<div className="row">
<div className="col-md-12">
<input type="file" accept="image/*" onChange={this.handleChange} />
</div>
</div>
<div className="row">
<div className="col-md-4 image" >
{
this.state.file&&(
<img className="img-responsive" src={this.state.dataURL} ref={this.imageRef} style={{border:'2px dashed green'}}/>
)
}
</div>
<div className="col-md-4 clip" onMouseDown={this.handleMouseDown} onMouseMove={this.handleMouseMove} onMouseUp={this.handleMouseUp}>
{
this.state.file&&(
<>
<div style={{position:'relative'}}>
<canvas ref={this.canvasRef} width="300px" height="300px" style={{border:'2px dashed blue'}}></canvas>
<div style={{width:100,height:100,backgroundColor:'yellow',opacity:.3,position:'absolute',left:100,top:100}}></div>
</div>
<div className="btn-group" role="group" >
<button type="button" className="btn btn-default" onClick={()=>this.bigger()}>放大</button>
<button type="button" className="btn btn-default" onClick={()=>this.smaller()}>缩小</button>
<button type="button" className="btn btn-default" onClick={()=>this.confirm()}>确定</button>
</div>
</>
)
}
</div>
<div className="col-md-4 avatar" >
{
this.state.file&&(
<>
<img ref={this.avatarRef} style={{border:'2px dashed pink',width:'100px',height:'100px'}}/>
<p> <button className="btn btn-default" onClick={this.upload}>上传</button></p>
</>
)
}
</div>
</div>
</div>
)
}
}
server.js
let express = require('express');
let path = require('path');
let cors = require('cors');
let app = express();
app.use(cors());
app.use(express.static(path.join(__dirname,'public')));
const multer = require('multer');
app.use(multer({dest: './uploads'}).single('avatar'));
app.post('/upload', function(req, res){
res.json({success:true});
});
app.listen(8080,()=>{
console.log('server started at port 8080');
});
import React from 'react';
import ReactDOM from 'react-dom';
import Audio from './Audio';
ReactDOM.render(
<Audio />,
document.getElementById('root')
);
src\Audio.js
import React from 'react';
import axios from 'axios';
export default class Audio extends React.Component {
audioRef = React.createRef()
audioClipRef = React.createRef()
startRef = React.createRef()
endRef = React.createRef()
clip = async () => {
this.worker = createWorker('/ffmpeg-worker-mp4.js');
let response = await axios({ url: '/song.mp3', method: 'get', responseType: 'arraybuffer' });
let originArrayBuffer = response.data;
let start = parseInt(this.startRef.current.value);
let end = parseInt(this.endRef.current.value);
let duration = end - start;
let resultArrayBuffer = (await toPromise(
this.worker,
getClipCommand(originArrayBuffer, start, duration)
)).data.data.MEMFS[0].data;
let clipBlob = audioBufferToBlob(resultArrayBuffer);
let audio= this.audioClipRef.current
audio.src = URL.createObjectURL(clipBlob);
audio.load();
audio.play();
};
render() {
return (
<div>
<input ref={this.startRef} defaultValue={0} />
<input ref={this.endRef} defaultValue={10} />
<button type="button" className="primary" onClick={this.clip}>clip</button>
<audio ref={this.audioRef} controls src="/song.mp3"></audio>
<audio ref={this.audioClipRef} controls></audio>
</div>
)
}
}
function createWorker(workerPath) {
return new Worker(workerPath);
}
function getClipCommand(arrayBuffer, start = 0, duration = 10) {
return {
type: "run",
arguments: `-ss ${start} -i input.mp3 ${
duration ? `-t ${duration} ` : ""
}-acodec copy output.mp3`.split(" "),
MEMFS: [
{
data: new Uint8Array(arrayBuffer),
name: "input.mp3"
}
]
};
}
function toPromise(worker, info) {
return new Promise((resolve) => {
const onSuccess = function (event) {
switch (event.data.type) {
case "done":
worker.removeEventListener("message", onSuccess);
resolve(event);
break;
default:
break;
}
};
worker.addEventListener("message", onSuccess);
info && worker.postMessage(info);
});
}
function audioBufferToBlob(arrayBuffer) {
const file = new File([arrayBuffer], 'test.mp3', {
type: 'audio/mp3',
});
return file;
}