Javascript React Native中音频进度条的实现

Javascript React Native中音频进度条的实现,javascript,react-native,expo,Javascript,React Native,Expo,我一直在尝试使用Expo在react native中构建一个媒体播放器,以便能够在我的音乐项目中播放音频 我已经成功地将一个与首选设计结合在一起,但我仍然有一个很大的限制。我会喜欢实现一个进度条,它将显示歌曲播放了多远 这是我的播放器设计。其次,我如何用这个进度条代替IOS render() { return ( <View > <View style={styles.container} >

我一直在尝试使用Expo在react native中构建一个媒体播放器,以便能够在我的音乐项目中播放音频

我已经成功地将一个与首选设计结合在一起,但我仍然有一个很大的限制。我会喜欢实现一个进度条,它将显示歌曲播放了多远

这是我的播放器设计。其次,我如何用这个进度条代替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>
        );
    }
我的主要目标是让手机上的小玩家接近这一点。

我正在使用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,
  }
});