add audio decoder

This commit is contained in:
xmcp
2018-08-21 21:04:30 +08:00
parent e44cbaaf13
commit c118e64c7a
7 changed files with 1410 additions and 6 deletions

View File

@@ -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 Normal file
View File

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

View File

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

View File

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