Browse Source

add audio decoder

dev
xmcp 7 years ago
parent
commit
c118e64c7a
  1. 5
      package-lock.json
  2. 6
      package.json
  3. 1301
      public/amr_all.min.js
  4. 1
      src/App.js
  5. 90
      src/AudioWidget.js
  6. 9
      src/Flows.js
  7. 4
      src/index.css

5
package-lock.json generated

@ -6325,6 +6325,11 @@
"strip-bom": "2.0.0"
}
},
"load-script": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
"integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ="
},
"loader-fs-cache": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz",

6
package.json

@ -3,13 +3,13 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"load-script": "^1.0.0",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-lazyload": "^2.3.0",
"react-lazyload": "latest",
"react-linkify": "^0.2.2",
"react-scripts": "1.1.4",
"react-timeago": "^4.1.9",
"react-lazyload": "latest"
"react-timeago": "^4.1.9"
},
"scripts": {
"start": "react-scripts start",

1301
public/amr_all.min.js vendored

File diff suppressed because it is too large Load Diff

1
src/App.js

@ -39,6 +39,7 @@ class App extends Component {
<Flow key={this.state.flow_render_key}
callback={this.show_sidebar.bind(this)} search_text={this.state.search_text}
/>
<br />
</div>
<Sidebar do_close={()=>{
this.setState({

90
src/AudioWidget.js

@ -0,0 +1,90 @@
import React, {Component} from 'react';
import load from 'load-script';
window.audio_cache={};
function load_amrnb() {
return new Promise((resolve,reject)=>{
if(window.AMR)
resolve();
else
load('amr_all.min.js', (err)=>{
if(err)
reject(err);
else
resolve();
});
});
}
export class AudioWidget extends Component {
constructor(props) {
super(props);
this.state={
url: this.props.src,
state: 'loading',
data: null,
};
}
componentDidMount() {
this.load();
}
load() {
if(window.audio_cache[this.state.url]) {
this.setState({
state: 'loaded',
data: window.audio_cache[this.state.url],
});
return;
}
console.log('fetching audio',this.state.url);
Promise.all([
fetch(this.state.url),
load_amrnb(),
])
.then((res)=>{
res[0].blob().then((blob)=>{
const reader=new FileReader();
reader.onload=(event)=>{
const raw=new window.AMR().decode(event.target.result);
if(!raw) {
alert('audio decoding failed');
return;
}
const wave=window.PCMData.encode({
sampleRate: 8000,
channelCount: 1,
bytesPerSample: 2,
data: raw
});
const binary_wave=new Uint8Array(wave.length);
for(let i=0;i<wave.length;i++)
binary_wave[i]=wave.charCodeAt(i);
const objurl=URL.createObjectURL(new Blob([binary_wave], {type: 'audio/wav'}));
window.audio_cache[this.state.url]=objurl;
this.setState({
state: 'loaded',
data: objurl,
});
};
reader.readAsBinaryString(blob);
});
this.setState({
state: 'decoding',
});
});
}
render() {
if(this.state.state==='loading')
return (<p><audio controls />&nbsp;正在下载</p>);
else if(this.state.state==='decoding')
return (<p><audio controls />&nbsp;正在解码</p>);
else if(this.state.state==='loaded')
return (<p><audio src={this.state.data} controls /></p>);
}
}

9
src/Flows.js

@ -3,11 +3,14 @@ import {ColorPicker} from './color_picker';
import {Time, TitleLine, AutoLink} from './Common.js';
import './Flows.css';
import LazyLoad from 'react-lazyload';
import {AudioWidget} from './AudioWidget.js';
const IMAGE_BASE='http://www.pkuhelper.com/services/pkuhole/images/';
const AUDIO_BASE='http://www.pkuhelper.com/services/pkuhole/audios/';
const AUDIO_BASE='/audio_proxy/';
const API_BASE=window.location.protocol==='https:' ? '/api_proxy' : 'http://www.pkuhelper.com:10301/services/pkuhole';
const SEARCH_PAGESIZE=50;
const CLICKABLE_TAGS={a: true, audio: true};
function Reply(props) {
return (
@ -42,7 +45,7 @@ function FlowItem(props) {
</div>
<AutoLink text={props.info.text} />
{props.info.type==='image' ? <img src={IMAGE_BASE+props.info.url} /> : null}
{props.info.type==='audio' ? <audio src={AUDIO_BASE+props.info.url} /> : null}
{props.info.type==='audio' ? <AudioWidget src={AUDIO_BASE+props.info.url} /> : null}
</div>
);
}
@ -88,7 +91,7 @@ class FlowItemRow extends Component {
// props.do_show_details
return (
<div className="flow-item-row" onClick={(event)=>{
if(event.target.tagName.toLowerCase()!=='a')
if(!CLICKABLE_TAGS[event.target.tagName.toLowerCase()])
this.props.callback(
'帖子详情',
<div className="flow-item-row sidebar-flow-item">

4
src/index.css

@ -29,4 +29,8 @@ input {
border-radius: 5px;
border: 1px solid black;
outline: none;
}
audio {
vertical-align: middle;
}
Loading…
Cancel
Save