使用react native製做的一款網絡音樂播放器

使用react native製做的一款網絡音樂播放器 javascript

基於第三方庫 react-native-video 設計
"react-native-video": "^1.0.0" html

 播放/暫停java

 快進/快退react

 循環模式(單曲,隨機,列表)git

 歌詞同步github

 進度條顯示算法

 播放時間json

 基本旋轉動畫react-native

 動畫bugapi

 安卓歌詞解析失敗

 其餘

使用的數據是百度音樂

 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('\n')   //按照換行符切數組
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('\n')   //按照換行符切數組
                        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: width,height:height,backgroundColor:'white',opacity:0.8}}/>

                    <View style = {{position:'absolute',width: width}}>
                        {/*膠片光盤*/}
                        <Image source={require('./image/膠片盤.png')} style={{width:220,height:220,alignSelf:'center'}}/>

                        {/*旋轉小圖*/}
                        <Animated.Image
                            ref = 'myAnimate'
                            style={{width: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={{width: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={{width:30,height:30}}/>
                            </TouchableOpacity>

                            <TouchableOpacity onPress={()=>this.playAction()}>
                                <Image source={this.state.isplayBtn} style={{width:30,height:30}}/>
                            </TouchableOpacity>

                            <TouchableOpacity onPress={()=>this.nextAction(this.state.currentIndex + 1)}>
                                <Image source={require('./image/下一首.png')} style={{width: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

相關文章
相關標籤/搜索