add audio decoder
This commit is contained in:
@@ -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
90
src/AudioWidget.js
Normal 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 /> 正在下载……</p>);
|
||||
else if(this.state.state==='decoding')
|
||||
return (<p><audio controls /> 正在解码……</p>);
|
||||
else if(this.state.state==='loaded')
|
||||
return (<p><audio src={this.state.data} controls /></p>);
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -29,4 +29,8 @@ input {
|
||||
border-radius: 5px;
|
||||
border: 1px solid black;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
audio {
|
||||
vertical-align: middle;
|
||||
}
|
||||
Reference in New Issue
Block a user