1.生成项目 #

create-react-app zhufengbinary
cd zhufengbinary
yarn start

browserbinary2

2.ArrayBuffer #

bytearray

//创建一个长度为8个字节的buffer
const buffer = new ArrayBuffer(8);
console.log(buffer.byteLength);

3.TypedArray #

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 位无符号整数

TypedArray

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

4.DataView对象 #

new DataView(buffer [, byteOffset [, byteLength]])

DataView

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

5.Blob #

<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>

6. Object 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>

7.图片预览和裁剪上传 #

7.1 src\index.js #

import React from 'react';
import ReactDOM from 'react-dom';
import Image from './Image';

ReactDOM.render(
  <Image />,
  document.getElementById('root')
);

7.2 src\Image.js #

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>
       )
   }   
}

7.3 server.js #

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');
});

8.音频的裁剪和预览 #

8.1 index.js #

import React from 'react';
import ReactDOM from 'react-dom';
import Audio from './Audio';

ReactDOM.render(
  <Audio />,
  document.getElementById('root')
);

8.2 src\Audio.js #

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;
}