Javascript React Native中音频进度条的实现
我一直在尝试使用Expo在react native中构建一个媒体播放器,以便能够在我的音乐项目中播放音频 我已经成功地将一个与首选设计结合在一起,但我仍然有一个很大的限制。我会喜欢实现一个进度条,它将显示歌曲播放了多远 这是我的播放器设计。其次,我如何用这个进度条代替IOSJavascript React Native中音频进度条的实现,javascript,react-native,expo,Javascript,React Native,Expo,我一直在尝试使用Expo在react native中构建一个媒体播放器,以便能够在我的音乐项目中播放音频 我已经成功地将一个与首选设计结合在一起,但我仍然有一个很大的限制。我会喜欢实现一个进度条,它将显示歌曲播放了多远 这是我的播放器设计。其次,我如何用这个进度条代替IOS render() { return ( <View > <View style={styles.container} >
render() {
return (
<View >
<View style={styles.container} >
<Image
style={styles.imageStyle}
source={{uri: this.state.coverName || this.MusicPlayer.getCurrentItemCover()}}
/>
<View >
<Text style = {styles.artistName}> {this.state.artistName || this.MusicPlayer.getCurrentItemArtistName()}</Text>
</View>
<View style={{paddingRight:2, paddingLeft:2}}>
<Text style={styles.songStyle}> {this.state.title || this.MusicPlayer.getCurrentSongTitle()}</Text>
</View>
<ProgressBarAndroid style={{marginLeft:10, marginRight:10}} styleAttr="Horizontal" color="#2196F3" indeterminate={false} progress={0.5} />
<View style={{flexDirection:'row', padding:10, alignItems:'center', justifyContent:'center'}}>
<Text style={styles.iconStyle2} onPress={this.playPrev}>
<Feather name="rewind" size={20} style={styles.text} />
</Text>
{this.state.playing?
<Text style={styles.iconStyle2} onPress={this.startStopPlay}>
<Feather name="pause" size={24} style={styles.text} />
</Text>
:
<Text style={styles.iconStyle2} onPress={this.startStopPlay}>
<Feather name="play-circle" size={24} style={styles.text} />
</Text>
}
<Text style={styles.iconStyle2} onPress={this.playNext}>
<Feather name="fast-forward" size={20} style={styles.text} />
</Text>
</View>
</View>
</View>
);
}
我的主要目标是让手机上的小玩家接近这一点。
我正在使用React native Expo版本。
音乐播放器Android和ios的完整代码均处于工作状态
SeekBar.js
import React, { Component } from 'react';
import { defaultString } from '../String/defaultStringValue';
import {
View,
Text,
StyleSheet,
Image,
Slider,
TouchableOpacity,
} from 'react-native';
function pad(n, width, z = 0) {
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
const minutesAndSeconds = (position) => ([
pad(Math.floor(position / 60), 2),
pad(position % 60, 2),
]);
const SeekBar = ({
trackLength,
currentPosition,
onSeek,
onSlidingStart,
}) => {
const elapsed = minutesAndSeconds(currentPosition);
const remaining = minutesAndSeconds(trackLength - currentPosition);
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<Text style={[styles.text, { color: defaultString.darkColor }]}>
{elapsed[0] + ":" + elapsed[1]}
</Text>
<View style={{ flex: 1 }} />
<Text style={[styles.text, { width: 40, color: defaultString.darkColor }]}>
{trackLength > 1 && "-" + remaining[0] + ":" + remaining[1]}
</Text>
</View>
<Slider
maximumValue={Math.max(trackLength, 1, currentPosition + 1)}
onSlidingStart={onSlidingStart}
onSlidingComplete={onSeek}
value={currentPosition}
minimumTrackTintColor={defaultString.darkColor}
maximumTrackTintColor={defaultString.lightGrayColor}
thumbStyle={styles.thumb}
trackStyle={styles.track}
/>
</View>
);
};
export default SeekBar;
const styles = StyleSheet.create({
slider: {
marginTop: -12,
},
container: {
paddingLeft: 16,
paddingRight: 16,
paddingTop: 16,
},
track: {
height: 2,
borderRadius: 1,
},
thumb: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: defaultString.darkColor,
},
text: {
color: 'rgba(255, 255, 255, 0.72)',
fontSize: 12,
textAlign: 'center',
}
});
import React,{Component}来自'React';
从“../String/defaultStringValue”导入{defaultString};
进口{
看法
文本,
样式表,
形象,,
滑块,
可触摸不透明度,
}从“反应本机”;
功能板(n,宽度,z=0){
n=n+'';
返回n.length>=width?n:新数组(width-n.length+1);
}
常数分钟和秒=(位置)=>([
垫(数学地板(位置/60),2),
衬垫(位置%60,2),
]);
常数SeekBar=({
轨道长度,
当前位置,
星期一,
onSlidingStart,
}) => {
持续时间=分钟和秒(当前位置);
剩余常数=分钟和秒(轨迹长度-当前位置);
返回(
{经过的[0]+“:“+经过的[1]}
{trackLength>1&&“-”+剩余[0]+:“+剩余[1]}
);
};
导出默认SeekBar;
const styles=StyleSheet.create({
滑块:{
玛金托普:-12,
},
容器:{
paddingLeft:16,
paddingRight:16,
paddingTop:16,
},
轨道:{
身高:2,
边界半径:1,
},
拇指:{
宽度:10,
身高:10,
边界半径:5,
背景颜色:defaultString.darkColor,
},
正文:{
颜色:“rgba(255,255,255,0.72)”,
尺寸:12,
textAlign:'中心',
}
});
Player.js
import React, { Component } from 'react';
import {
View,
Text,
StatusBar,
} from 'react-native';
import Header from './Header';
import AlbumArt from './AlbumArt';
import TrackDetails from './TrackDetails';
import SeekBar from './SeekBar';
import Controls from './Controls';
import Video from 'react-native-video';
export default class Player extends Component {
constructor(props) {
super(props);
this.state = {
paused: true,
totalLength: 1,
currentPosition: 0,
selectedTrack: 0,
repeatOn: false,
shuffleOn: false,
};
}
setDuration(data) {
this.setState({ totalLength: Math.floor(data.duration) });
}
setTime(data) {
this.setState({ currentPosition: Math.floor(data.currentTime) });
}
seek(time) {
time = Math.round(time);
this.refs.audioElement && this.refs.audioElement.seek(time);
this.setState({
currentPosition: time,
paused: false,
});
}
onBack() {
if (this.state.currentPosition < 10 && this.state.selectedTrack > 0) {
this.refs.audioElement && this.refs.audioElement.seek(0);
this.setState({ isChanging: true });
setTimeout(() => this.setState({
currentPosition: 0,
paused: false,
totalLength: 1,
isChanging: false,
selectedTrack: this.state.selectedTrack - 1,
}), 0);
} else {
this.refs.audioElement.seek(0);
this.setState({
currentPosition: 0,
});
}
}
onForward() {
if (this.state.selectedTrack < this.props.tracks.length - 1) {
this.refs.audioElement && this.refs.audioElement.seek(0);
this.setState({ isChanging: true });
setTimeout(() => this.setState({
currentPosition: 0,
totalLength: 1,
paused: false,
isChanging: false,
selectedTrack: this.state.selectedTrack + 1,
}), 0);
}
}
render() {
const track = this.props.tracks[this.state.selectedTrack];
const video = this.state.isChanging ? null : (
<Video source={{ uri: track.audioUrl }} // Can be a URL or a local file.
ref="audioElement"
playInBackground={true}
playWhenInactive={true}
paused={this.state.paused} // Pauses playback entirely.
resizeMode="cover" // Fill the whole screen at aspect ratio.
repeat={true} // Repeat forever.
onLoadStart={this.loadStart} // Callback when video starts to load
onLoad={this.setDuration.bind(this)} // Callback when video loads
onProgress={this.setTime.bind(this)} // Callback every ~250ms with currentTime
onEnd={this.onEnd} // Callback when playback finishes
onError={this.videoError} // Callback when video cannot be loaded
style={styles.audioElement} />
);
return (
<View style={styles.container}>
{/* <StatusBar hidden={true} /> */}
{/* <Header message="Playing From Charts" /> */}
<AlbumArt url={track.albumArtUrl} />
<TrackDetails title={track.title} artist={track.artist} />
<SeekBar
onSeek={this.seek.bind(this)}
trackLength={this.state.totalLength}
onSlidingStart={() => this.setState({ paused: true })}
currentPosition={this.state.currentPosition}
/>
<Controls
onPressRepeat={() => this.setState({ repeatOn: !this.state.repeatOn })}
repeatOn={this.state.repeatOn}
shuffleOn={this.state.shuffleOn}
forwardDisabled={this.state.selectedTrack === this.props.tracks.length - 1}
onPressShuffle={() => this.setState({ shuffleOn: !this.state.shuffleOn })}
onPressPlay={() => this.setState({ paused: false })}
onPressPause={() => this.setState({ paused: true })}
onBack={this.onBack.bind(this)}
onForward={this.onForward.bind(this)}
paused={this.state.paused} />
{video}
</View>
);
}
}
const styles = {
container: {
flex: 1,
backgroundColor: '#ffffff',
},
audioElement: {
height: 0,
width: 0,
}
};
import React,{Component}来自'React';
进口{
看法
文本,
状态栏,
}从“反应本机”;
从“./头”导入头;
从“/AlbumArt”导入AlbumArt;
从“/TrackDetails”导入TrackDetails;
从“/SeekBar”导入SeekBar;
从“./Controls”导入控件;
从“react native Video”导入视频;
导出默认类播放器扩展组件{
建造师(道具){
超级(道具);
此.state={
是的,
总长度:1,
当前位置:0,
已选择跟踪:0,
重复:错,
shuffleOn:错,
};
}
设置持续时间(数据){
this.setState({totalLength:Math.floor(data.duration)});
}
设定时间(数据){
this.setState({currentPosition:Math.floor(data.currentTime)});
}
寻找(时间){
时间=数学。四舍五入(时间);
this.refs.audioElement&&this.refs.audioElement.seek(时间);
这是我的国家({
当前位置:时间,
暂停:错,
});
}
onBack(){
if(this.state.currentPosition<10&&this.state.selectedTrack>0){
this.refs.audioElement&&this.refs.audioElement.seek(0);
this.setState({isChanging:true});
setTimeout(()=>this.setState({
当前位置:0,
暂停:错,
总长度:1,
Ischange:错,
selectedTrack:this.state.selectedTrack-1,
}), 0);
}否则{
this.refs.audioElement.seek(0);
这是我的国家({
当前位置:0,
});
}
}
onForward(){
if(this.state.selectedTrackthis.setState({
当前位置:0,
总长度:1,
暂停:错,
Ischange:错,
selectedTrack:this.state.selectedTrack+1,
}), 0);
}
}
render(){
const track=this.props.tracks[this.state.selectedTrack];
const video=this.state.isChanging?空:(
);
返回(
{/* */}
{/* */}
this.setState({暂停:true})}
currentPosition={this.state.currentPosition}
/>
this.setState({repeatOn:!this.state.repeatOn})}
repeatOn={this.state.repeatOn}
shuffleOn={this.state.shuffleOn}
forwardDisabled={this.state.selectedTrack===this.props.tracks.length-1}
onPressShuffle={()=>this.setState({shuffleOn:!this.state.shuffleOn})}
onPressPlay={()=>this.setState({暂停:false})}
onPressPause={()=>this.setState({paused:true})}
onBack={this.onBack.bind(this)}
onForward={this.onForward.bind(this)}
暂停={this.state.paused}/>
{视频}
);
}
}
常量样式={
容器:{
弹性:1,
背景颜色:“#ffffff”,
},
音频元素:{
高度:0,,
宽度:0,
}
};
AlbumArt.js
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
Image,
TouchableHighlight,
TouchableOpacity,
Dimensions,
} from 'react-native';
const AlbumArt = ({
url,
onPress
}) => (
<View style={styles.container}>
<TouchableOpacity onPress={onPress}>
<View
style={[styles.image, {
elevation: 10, shadowColor: '#d9d9d9',
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 1,
shadowRadius: 2,
borderRadius: 20,
backgroundColor: '#ffffff'
}]}
>
<Image
style={[styles.image, { borderRadius: 20 }]}
source={{ uri: url }}
/>
</View>
</TouchableOpacity>
</View>
);
export default AlbumArt;
const { width, height } = Dimensions.get('window');
const imageSize = width - 100;
const styles = StyleSheet.create({
container: {
alignItems: 'center',
marginTop: 30,
paddingLeft: 24,
paddingRight: 24,
},
image: {
width: imageSize,
height: imageSize,
},
})
import React,{Component}来自'React';
进口{
看法
文本,
样式表,
形象,,
触控高光,
可触摸不透明度,
尺寸,
}从“反应本机”;
常量相册艺术=({
网址,
onPress
}) => (
);
导出默认相册;
const{width,height}=Dimensions.get('window');
常量imageSize=宽度-100;
const styles=StyleSheet.create({
容器:{
对齐项目:“居中”,
玛金托普:30,
paddingLeft:24,
paddingRight:24,
},
图片:{
宽度:图像大小,
高度:图像大小,
},
})
App.js
import React, { Component } from 'react';
import Player from './Player';
import { BackHandler } from 'react-native';
import i18n from '../../Assets/I18n/i18n';
import { Actions } from 'react-native-router-flux';
export default class MusicPlayer extends Component {
constructor(props) {
super(props);
const { navigation } = this.props;
this.state = {
song: navigation.getParam('songid')
};
this.props.navigation.setParams({
title: i18n.t('Panchkhan')
})
}
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = () => {
Actions.pop();
return true;
};
render() {
const TRACKS = [
{
title: 'Stressed Out',
artist: 'Twenty One Pilots',
albumArtUrl: "https://cdn-images-1.medium.com/max/1344/1*fF0VVD5cCRam10rYvDeTOw.jpeg",
audioUrl: this.state.song
}
];
return <Player tracks={TRACKS} />
}
}
import React,{Component}来自'React';
从“./Player”导入播放器;
从“react native”导入{BackHandler};
从“../../Assets/i18n/i18n”导入i18n;
从“react native router flux”导入{Actions};
导出默认类MusicLayer扩展组件{
建造师(道具){
超级(道具);
const{navigation}=this.props;
此.state={
歌曲:navigation.ge
import React, { Component } from 'react';
import Player from './Player';
import { BackHandler } from 'react-native';
import i18n from '../../Assets/I18n/i18n';
import { Actions } from 'react-native-router-flux';
export default class MusicPlayer extends Component {
constructor(props) {
super(props);
const { navigation } = this.props;
this.state = {
song: navigation.getParam('songid')
};
this.props.navigation.setParams({
title: i18n.t('Panchkhan')
})
}
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = () => {
Actions.pop();
return true;
};
render() {
const TRACKS = [
{
title: 'Stressed Out',
artist: 'Twenty One Pilots',
albumArtUrl: "https://cdn-images-1.medium.com/max/1344/1*fF0VVD5cCRam10rYvDeTOw.jpeg",
audioUrl: this.state.song
}
];
return <Player tracks={TRACKS} />
}
}
import React, { Component } from 'react';
import { defaultString } from '../String/defaultStringValue';
import {
View,
Text,
StyleSheet,
Image,
TouchableOpacity,
} from 'react-native';
const Controls = ({
paused,
shuffleOn,
repeatOn,
onPressPlay,
onPressPause,
onBack,
onForward,
onPressShuffle,
onPressRepeat,
forwardDisabled,
}) => (
<View style={styles.container}>
<TouchableOpacity activeOpacity={0.0} onPress={onPressShuffle}>
<Image style={[{ tintColor: defaultString.darkColor } , styles.secondaryControl, shuffleOn ? [] : styles.off]}
source={require('../img/ic_shuffle_white.png')} />
</TouchableOpacity>
<View style={{ width: 40 }} />
<TouchableOpacity onPress={onBack}>
<Image style={{ tintColor: defaultString.darkColor }} source={require('../img/ic_skip_previous_white_36pt.png')} />
</TouchableOpacity>
<View style={{ width: 20 }} />
{!paused ?
<TouchableOpacity onPress={onPressPause}>
<View style={styles.playButton}>
<Image style={{ tintColor: defaultString.darkColor }} source={require('../img/ic_pause_white_48pt.png')} />
</View>
</TouchableOpacity> :
<TouchableOpacity onPress={onPressPlay}>
<View style={styles.playButton}>
<Image style={{ tintColor: defaultString.darkColor }} source={require('../img/ic_play_arrow_white_48pt.png')} />
</View>
</TouchableOpacity>
}
<View style={{ width: 20 }} />
<TouchableOpacity onPress={onForward}
disabled={forwardDisabled}>
<Image style={[forwardDisabled && { opacity: 0.3 }, { tintColor: defaultString.darkColor }]}
source={require('../img/ic_skip_next_white_36pt.png')} />
</TouchableOpacity>
<View style={{ width: 40 }} />
<TouchableOpacity activeOpacity={0.0} onPress={onPressRepeat}>
<Image style={[{ tintColor: defaultString.darkColor }, styles.secondaryControl, repeatOn ? [] : styles.off]}
source={require('../img/ic_repeat_white.png')} />
</TouchableOpacity>
</View>
);
export default Controls;
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingTop: 8,
},
playButton: {
height: 72,
width: 72,
borderWidth: 1,
borderColor: defaultString.darkColor,
borderRadius: 72 / 2,
alignItems: 'center',
justifyContent: 'center',
},
secondaryControl: {
height: 18,
width: 18,
},
off: {
opacity: 0.30,
}
})
import React, { Component } from 'react';
import { defaultString } from '../String/defaultStringValue';
import {
View,
Text,
StyleSheet,
Image,
TouchableHighlight,
TouchableOpacity,
Dimensions,
} from 'react-native';
const TrackDetails = ({
title,
artist,
onAddPress,
onMorePress,
onTitlePress,
onArtistPress,
}) => (
<View style={styles.container}>
{/* <TouchableOpacity onPress={onAddPress}>
<Image style={styles.button}
source={require('../img/ic_add_circle_outline_white.png')} />
</TouchableOpacity> */}
<View style={styles.detailsWrapper}>
<Text style={styles.title} onPress={onTitlePress}>{title}</Text>
<Text style={styles.artist} onPress={onArtistPress}>{artist}</Text>
</View>
{/* <TouchableOpacity onPress={onMorePress}>
<View style={styles.moreButton}>
<Image style={styles.moreButtonIcon}
source={require('../img/ic_more_horiz_white.png')} />
</View>
</TouchableOpacity> */}
</View>
);
export default TrackDetails;
const styles = StyleSheet.create({
container: {
paddingTop: 24,
flexDirection: 'row',
paddingLeft: 20,
alignItems: 'center',
paddingRight: 20,
},
detailsWrapper: {
justifyContent: 'center',
alignItems: 'center',
flex: 1,
},
title: {
fontSize: 16,
fontWeight: 'bold',
color: defaultString.darkColor,
textAlign: 'center',
},
artist: {
color: defaultString.darkColor,
fontSize: 12,
marginTop: 4,
},
button: {
opacity: 0.72,
},
moreButton: {
borderColor: 'rgb(255, 255, 255)',
borderWidth: 2,
opacity: 0.72,
borderRadius: 10,
width: 20,
height: 20,
alignItems: 'center',
justifyContent: 'center',
},
moreButtonIcon: {
height: 17,
width: 17,
}
});