我一直在尝试使用Expo构建本机以响应本机的媒体播放器,以便能够在我的音乐项目中播放音频。
我已经成功地将首选的设计与其他工具结合在一起,但是我仍然有很大的局限性。我很想实现一个进度条,该进度条将显示歌曲的播放距离。
这是我的播放器设计。其次,如何用此进度条代替IOS?
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>
);
}
}
我的播放功能
startPlay = async (index = this.index, playing = false) => {
const url = this.list[index].url;
this.index = index;
console.log(url);
// Checking if now playing music, if yes stop that
if(playing) {
await this.soundObject.stopAsync();
} else {
// Checking if item already loaded, if yes just play, else load music before play
if(this.soundObject._loaded) {
await this.soundObject.playAsync();
} else {
await this.soundObject.loadAsync(url);
await this.soundObject.playAsync();
}
}
};
我正在使用React本机Expo版本。
答案 0 :(得分:0)
音乐播放器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',
}
});
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,
}
};
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,
},
})
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} />
}
}
Controls.js
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,
}
})
controlDetails.js
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,
}
});