• 使用react native制作的一款网络音乐播放器


    使用react native制作的一款网络音乐播放器 

    基于第三方库 react-native-video 设计
    "react-native-video": "^1.0.0" 

     播放/暂停

     快进/快退

     循环模式(单曲,随机,列表)

     歌词同步

     进度条显示

     播放时间

     基本旋转动画

     动画bug

     安卓歌词解析失败

     其他

    使用的数据是百度音乐

     http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=2&size=10&offset=0 //总列表
     http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.song.lry&songid=213508 //歌词文件
     http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.song.play&songid=877578 //播放

    更多:http://67zixue.com/home/article/detail/id/22.html

    主要代码
    把秒数转换为时间类型:
        //把秒数转换为时间类型
        formatTime(time) {
            // 71s -> 01:11
            let min = Math.floor(time / 60)
            let second = time - min * 60
            min = min >= 10 ? min : '0' + min
            second = second >= 10 ? second : '0' + second
            return min + ':' + second
        } 
    
    
    
     歌词:
    [ti:阳光总在风雨后] [ar:许美静] [al:都是夜归人] [00:05.97]阳光总在风雨后 [00:14.31]演唱:许美静......
    
    
    

     拿到当前歌曲的歌词后,如上,把这段字符截成一个这样的数组

     其算法如下:

    let lry = responseJson.lrcContent
    let lryAry = lry.split('
    ')   //按照换行符切数组
    lryAry.forEach(function (val, index) {
        var obj = {}   //用于存放时间
        val = val.replace(/(^s*)|(s*$)/g, '')    //正则,去除前后空格
        let indeofLastTime = val.indexOf(']')  // ]的下标
        let timeStr = val.substring(1, indeofLastTime) //把时间切出来 0:04.19
        let minSec = ''
        let timeMsIndex = timeStr.indexOf('.')  // .的下标
        if (timeMsIndex !== -1) {
            //存在毫秒 0:04.19
            minSec = timeStr.substring(1, val.indexOf('.'))  // 0:04.
            obj.ms = parseInt(timeStr.substring(timeMsIndex + 1, indeofLastTime))  //毫秒值 19
        } else {
            //不存在毫秒 0:04
            minSec = timeStr
            obj.ms = 0
        }
        let curTime = minSec.split(':')  // [0,04]
        obj.min = parseInt(curTime[0])   //分钟 0
        obj.sec = parseInt(curTime[1])   //秒钟 04
        obj.txt = val.substring(indeofLastTime + 1, val.length) //歌词文本: 留下唇印的嘴
        obj.txt = obj.txt.replace(/(^s*)|(s*$)/g, '')
        obj.dis = false
        obj.total = obj.min * 60 + obj.sec + obj.ms / 100   //总时间
        if (obj.txt.length > 0) {
            lyrObj.push(obj)
        }
    })
    

      

    歌词显示:

     // 歌词
        renderItem() {
            // 数组
            var itemAry = [];
            for (var i = 0; i < lyrObj.length; i++) {
                var item = lyrObj[i].txt
                if (this.state.currentTime.toFixed(2) > lyrObj[i].total) {
                    //正在唱的歌词
                    itemAry.push(
                        <View key={i} style={styles.itemStyle}>
                            <Text style={{ color: 'blue' }}> {item} </Text>
                        </View>
                    );
                    _scrollView.scrollTo({x: 0,y:(25 * i),animated:false});
                }
                else {
                    //所有歌词
                    itemAry.push(
                        <View key={i} style={styles.itemStyle}>
                            <Text style={{ color: 'red' }}> {item} </Text>
                        </View>
                    )
                }
            }
    
            return itemAry;
        }
     
    

      

    其余什么播放/暂停.时间显示,快进/快退,进度条都是根据react-native-video 而来.

    完整代码:

    /**
     * Created by shaotingzhou on 2017/4/13.
     */
    
    import React, { Component } from 'react'
    import {
        AppRegistry,
        StyleSheet,
        Dimensions,
        Text,
        Image,
        View,
        Slider,
        TouchableOpacity,
        ScrollView,
        ActivityIndicator,
        Animated,
        Easing
    } from 'react-native'
    var {width,height} = Dimensions.get('window');
    import Video from 'react-native-video'
    var lyrObj = []   // 存放歌词
    var myAnimate;
    //       http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=2&size=10&offset=0    //总列表
    //       http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.song.lry&songid=213508   //歌词文件
    //       http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.song.play&songid=877578   //播放
    
    
    export default class Main extends Component {
    
        constructor(props) {
            super(props);
            this.spinValue = new Animated.Value(0)
            this.state = {
                songs: [],   //歌曲id数据源
                playModel:1,  // 播放模式  1:列表循环    2:随机    3:单曲循环
                btnModel:require('./image/列表循环.png'), //播放模式按钮背景图
                pic_small:'',    //小图
                pic_big:'',      //大图
                file_duration:0,    //歌曲长度
                song_id:'',     //歌曲id
                title:'',       //歌曲名字
                author:'',      //歌曲作者
                file_link:'',   //歌曲播放链接
                songLyr:[],     //当前歌词
                sliderValue: 0,    //Slide的value
                pause:false,       //歌曲播放/暂停
                currentTime: 0.0,   //当前时间
                duration: 0.0,     //歌曲时间
                currentIndex:0,    //当前第几首
                isplayBtn:require('./image/播放.png')  //播放/暂停按钮背景图
            }
        }
        //上一曲
        prevAction = (index) =>{
            this.recover()
            lyrObj = [];
            if(index == -1){
                index = this.state.songs.length - 1 // 如果是第一首就回到最后一首歌
            }
            this.setState({
                currentIndex:index  //更新数据
            })
            this.loadSongInfo(index)  //加载数据
        }
        //下一曲
        nextAction = (index) =>{
            this.recover()
            lyrObj = [];
            if(index == 10){
                index = 0 //如果是最后一首就回到第一首
            }
            this.setState({
                currentIndex:index,  //更新数据
            })
            this.loadSongInfo(index)   //加载数据
        }
        //换歌时恢复进度条 和起始时间
        recover = () =>{
            this.setState({
                sliderValue:0,
                currentTime: 0.0
            })
        }
        //播放模式 接收传过来的当前播放模式 this.state.playModel
        playModel = (playModel) =>{
            playModel++;
            playModel = playModel == 4 ? 1 : playModel
            //重新设置
            this.setState({
                playModel:playModel
            })
            //根据设置后的模式重新设置背景图片
            if(playModel == 1){
                this.setState({
                    btnModel:require('./image/列表循环.png'),
                })
            }else if(playModel ==  2){
                this.setState({
                    btnModel:require('./image/随机.png'),
                })
            }else{
                this.setState({
                    btnModel:require('./image/单曲循环.png'),
                })
            }
        }
        //播放/暂停
        playAction =() => {
            this.setState({
                pause: !this.state.pause
            })
            //判断按钮显示什么
            if(this.state.pause == true){
                this.setState({
                    isplayBtn:require('./image/播放.png')
                })
            }else {
                this.setState({
                    isplayBtn:require('./image/暂停.png')
                })
            }
    
        }
        //播放器每隔250ms调用一次
        onProgress =(data) => {
            let val = parseInt(data.currentTime)
            this.setState({
                sliderValue: val,
                currentTime: data.currentTime
            })
    
            //如果当前歌曲播放完毕,需要开始下一首
            if(val == this.state.file_duration){
                if(this.state.playModel == 1){
                    //列表 就播放下一首
                    this.nextAction(this.state.currentIndex + 1)
                }else if(this.state.playModel == 2){
                    let  last =  this.state.songs.length //json 中共有几首歌
                    let random = Math.floor(Math.random() * last)  //取 0~last之间的随机整数
                    this.nextAction(random) //播放
                }else{
                    //单曲 就再次播放当前这首歌曲
                    this.refs.video.seek(0) //让video 重新播放
                    _scrollView.scrollTo({x: 0,y:0,animated:false});
                }
            }
    
        }
        //把秒数转换为时间类型
        formatTime(time) {
            // 71s -> 01:11
            let min = Math.floor(time / 60)
            let second = time - min * 60
            min = min >= 10 ? min : '0' + min
            second = second >= 10 ? second : '0' + second
            return min + ':' + second
        }
        // 歌词
        renderItem() {
            // 数组
            var itemAry = [];
            for (var i = 0; i < lyrObj.length; i++) {
                var item = lyrObj[i].txt
                if (this.state.currentTime.toFixed(2) > lyrObj[i].total) {
                    //正在唱的歌词
                    itemAry.push(
                        <View key={i} style={styles.itemStyle}>
                            <Text style={{ color: 'blue' }}> {item} </Text>
                        </View>
                    );
                    _scrollView.scrollTo({x: 0,y:(25 * i),animated:false});
                }
                else {
                    //所有歌词
                    itemAry.push(
                        <View key={i} style={styles.itemStyle}>
                            <Text style={{ color: 'red' }}> {item} </Text>
                        </View>
                    )
                }
            }
    
            return itemAry;
        }
        // 播放器加载好时调用,其中有一些信息带过来
        onLoad = (data) => {
            this.setState({ duration: data.duration });
        }
    
        loadSongInfo = (index) => {
            //加载歌曲
            let songid =  this.state.songs[index]
            let url = 'http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.song.play&songid=' + songid
            fetch(url)
                .then((response) => response.json())
                .then((responseJson) => {
                    let songinfo = responseJson.songinfo
                    let bitrate = responseJson.bitrate
                    this.setState({
                        pic_small:songinfo.pic_small, //小图
                        pic_big:songinfo.pic_big,  //大图
                        title:songinfo.title,     //歌曲名
                        author:songinfo.author,   //歌手
                        file_link:bitrate.file_link,   //播放链接
                        file_duration:bitrate.file_duration //歌曲长度
                    })
    
                    //加载歌词
                    let url = 'http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.song.lry&songid=' + songid
                    fetch(url)
                        .then((response) => response.json())
                        .then((responseJson) => {
    
                            let lry = responseJson.lrcContent
                            let lryAry = lry.split('
    ')   //按照换行符切数组
                            lryAry.forEach(function (val, index) {
                                var obj = {}   //用于存放时间
                                val = val.replace(/(^s*)|(s*$)/g, '')    //正则,去除前后空格
                                let indeofLastTime = val.indexOf(']')  // ]的下标
                                let timeStr = val.substring(1, indeofLastTime) //把时间切出来 0:04.19
                                let minSec = ''
                                let timeMsIndex = timeStr.indexOf('.')  // .的下标
                                if (timeMsIndex !== -1) {
                                    //存在毫秒 0:04.19
                                    minSec = timeStr.substring(1, val.indexOf('.'))  // 0:04.
                                    obj.ms = parseInt(timeStr.substring(timeMsIndex + 1, indeofLastTime))  //毫秒值 19
                                } else {
                                    //不存在毫秒 0:04
                                    minSec = timeStr
                                    obj.ms = 0
                                }
                                let curTime = minSec.split(':')  // [0,04]
                                obj.min = parseInt(curTime[0])   //分钟 0
                                obj.sec = parseInt(curTime[1])   //秒钟 04
                                obj.txt = val.substring(indeofLastTime + 1, val.length) //歌词文本: 留下唇印的嘴
                                obj.txt = obj.txt.replace(/(^s*)|(s*$)/g, '')
                                obj.dis = false
                                obj.total = obj.min * 60 + obj.sec + obj.ms / 100   //总时间
                                if (obj.txt.length > 0) {
                                    lyrObj.push(obj)
                                }
                            })
                        })
    
                })
        }
    
    
        componentWillMount() {
            //先从总列表中获取到song_id保存
            fetch('http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.billboard.billList&type=2&size=10&offset=0')
                .then((response) => response.json())
                .then((responseJson) => {
                      var listAry = responseJson.song_list
                    var song_idAry = []; //保存song_id的数组
                    for(var i = 0;i<listAry.length;i++){
                          let song_id = listAry[i].song_id
                          song_idAry.push(song_id)
                      }
                    this.setState({
                        songs:song_idAry
                    })
                    this.loadSongInfo(0)   //预先加载第一首
                })
    
            this.spin()   //   启动旋转
    
        }
    
        //旋转动画
        spin () {
            this.spinValue.setValue(0)
            myAnimate = Animated.timing(
                this.spinValue,
                {
                    toValue: 1,
                    duration: 4000,
                    easing: Easing.linear
                }
            ).start(() => this.spin())
    
        }
    
        render() {
            //如果未加载出来数据 就一直转菊花
            if (this.state.file_link.length <= 0 ) {
                return(
                    <ActivityIndicator
                        animating={this.state.animating}
                        style={{flex: 1,alignItems: 'center',justifyContent: 'center'}}
                        size="large" />
                )
            }else{
                const spin = this.spinValue.interpolate({
                    inputRange: [0, 1],
                    outputRange: ['0deg', '360deg']
                })
    
    
                //数据加载出来
                return (
                    <View style={styles.container}>
                        {/*背景大图*/}
                        <Image source={{uri:this.state.pic_big}} style={{flex:1}}/>
                        {/*背景白色透明遮罩*/}
                        <View style = {{position:'absolute', width,height:height,backgroundColor:'white',opacity:0.8}}/>
    
                        <View style = {{position:'absolute', width}}>
                            {/*胶片光盘*/}
                            <Image source={require('./image/胶片盘.png')} style={{220,height:220,alignSelf:'center'}}/>
    
                            {/*旋转小图*/}
                            <Animated.Image
                                ref = 'myAnimate'
                                style={{140,height:140,marginTop: -180,alignSelf:'center',borderRadius: 140*0.5,transform: [{rotate: spin}]}}
                                source={{uri: this.state.pic_small}}
                            />
    
                            {/*播放器*/}
                            <Video
                                source={{uri: this.state.file_link}}
                                ref='video'
                                volume={1.0}
                                paused={this.state.pause}
                                onProgress={(e) => this.onProgress(e)}
                                onLoad={(e) => this.onLoad(e)}
                            />
                            {/*歌曲信息*/}
                            <View style={styles.playingInfo}>
                                {/*作者-歌名*/}
                                <Text>{this.state.author} - {this.state.title}</Text>
                                {/*时间*/}
                                <Text>{this.formatTime(Math.floor(this.state.currentTime))} - {this.formatTime(Math.floor(this.state.duration))}</Text>
                            </View>
                            {/*播放模式*/}
                            <View style = {{marginTop: 5,marginBottom:5,marginLeft: 20}}>
                                <TouchableOpacity onPress={()=>this.playModel(this.state.playModel)}>
                                    <Image source={this.state.btnModel} style={{20,height:20}}/>
                                </TouchableOpacity>
                            </View>
                            {/*进度条*/}
                            <Slider
                                ref='slider'
                                style={{ marginLeft: 10, marginRight: 10}}
                                value={this.state.sliderValue}
                                maximumValue={this.state.file_duration}
                                step={1}
                                minimumTrackTintColor='#FFDB42'
                                onValueChange={(value) => {
                                  this.setState({
                                      currentTime:value
                                  })
    							            }
    						            }
                                onSlidingComplete={(value) => {
    							             this.refs.video.seek(value)
    							        }}
                            />
                            {/*歌曲按钮*/}
                            <View style = {{flexDirection:'row',justifyContent:'space-around'}}>
                                <TouchableOpacity onPress={()=>this.prevAction(this.state.currentIndex - 1)}>
                                    <Image source={require('./image/上一首.png')} style={{30,height:30}}/>
                                </TouchableOpacity>
    
                                <TouchableOpacity onPress={()=>this.playAction()}>
                                    <Image source={this.state.isplayBtn} style={{30,height:30}}/>
                                </TouchableOpacity>
    
                                <TouchableOpacity onPress={()=>this.nextAction(this.state.currentIndex + 1)}>
                                    <Image source={require('./image/下一首.png')} style={{30,height:30}}/>
                                </TouchableOpacity>
                            </View>
    
                            {/*歌词*/}
                            <View style={{height:140,alignItems:'center'}}>
    
                                <ScrollView style={{position:'relative'}}
                                            ref={(scrollView) => { _scrollView = scrollView}}
                                >
                                    {this.renderItem()}
                                </ScrollView>
                            </View>
                        </View>
    
                    </View>
                )
            }
    
        }
    }
    
    const styles = StyleSheet.create({
        container: {
            flex: 1,
        },
        image: {
            flex: 1
        },
        playingControl: {
            flexDirection: 'row',
            alignItems: 'center',
            paddingTop: 10,
            paddingLeft: 20,
            paddingRight: 20,
            paddingBottom: 20
        },
        playingInfo: {
            flexDirection: 'row',
            alignItems:'stretch',
            justifyContent: 'space-between',
            paddingTop: 40,
            paddingLeft: 20,
            paddingRight: 20,
            backgroundColor:'rgba(255,255,255,0.0)'
        },
        text: {
            color: "black",
            fontSize: 22
        },
        modal: {
            height: 300,
            borderTopLeftRadius: 5,
            borderTopRightRadius: 5,
            paddingTop: 5,
            paddingBottom: 50
        },
        itemStyle: {
            paddingTop: 20,
            height:25,
            backgroundColor:'rgba(255,255,255,0.0)'
        }
    })
    

      

    github地址: https://github.com/pheromone/react-native-videoDemo

      

    
    
  • 相关阅读:
    Visual Studio LightSwitch
    Android 虚拟机与真机调试配置
    点击手机 menu 硬件按钮后的显示及处理
    Windows Phone 7 真机调试
    Android 调试
    Android 新建项目 页面
    今天我的Windows Phone 7 HTC HD7 手机 升级 NoDo 了 分享一下经验
    Activity 之间调用与参数传递
    Android widget 组件
    解决 warning: found plain 'id' attribute; did you mean the new 'android:id' name? 问题
  • 原文地址:https://www.cnblogs.com/shaoting/p/6705307.html
Copyright © 2020-2023  润新知