使用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('\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

  

												

使用react native制作的一款网络音乐播放器的更多相关文章

  1. swift3.0 简单直播和简单网络音乐播放器

    本项目采用swift3.0所写,适配iOS9.0+,所有界面均采用代码布局. 第一个tab写的是简单直播,传统MVC模式,第二个tab写的是简单网络音乐播放器.传说MVVM模式(至于血统是否纯正我就不 ...

  2. 我在 Gitee 上发现了一个简洁又好用的网络音乐播放器!

    这几天无聊的时候我想听听歌,但我想要找一个简单快速的网络音乐播放器来用用.这时我在 Gitee 上看见一个看上去不错的开源项目 -- Hi音乐. 项目链接:https://gitee.com/hi-j ...

  3. 22_Android中的本地音乐播放器和网络音乐播放器的编写,本地视频播放器和网络视频播放器,照相机案例,偷拍案例实现

    1 编写以下案例: 当点击了"播放"之后,在手机上的/mnt/sdcard2/natural.mp3就会播放. 2 编写布局文件activity_main.xml <Line ...

  4. Android实现网络音乐播放器

    本文是一个简单的音乐播放器 布局代码 <?xml version="1.0" encoding="utf-8"?> <RelativeLayo ...

  5. 解决React Native使用Fetch API请求网络报Network request failed

    问题来源: 1 . 在测试fetch数据请求时,Xcode9.0以上的无法请求https, 需要在Xcode中加载项目后修改Info.plist的相关配置,具体如下参考 问题及解决方法一模一样,不再重 ...

  6. 使用 原生js 制作插件 (javaScript音乐播放器)

    1.引用页面 index.html <!DOCTYPE html> <html lang="en"> <head> <meta chars ...

  7. Swift - 制作一个在线流媒体音乐播放器(使用StreamingKit库)

    在之前的文章中,我介绍了如何使用 AVPlayer 制作一个简单的音乐播放器(点击查看1.点击查看2).虽然这个播放器也可以播放网络音频,但其实际上是将音频文件下载到本地后再播放的. 本文演示如何使用 ...

  8. 【.NET 与树莓派】用 MPD 制作数字音乐播放器

    树莓派的日常家居玩法多多,制作一台属于自己的数字音乐播放机是其中的一种.严格上说,树莓派是没有声卡的,其板载的 3.5 mm 音频孔实际是通过 PWM 来实现音频输出的(通过算法让PWM信号变成模拟信 ...

  9. [MFC] MFC音乐播放器 傻瓜级教程 网络 搜索歌曲 下载

    >目录< >——————————————————————< 1.建立工程  1.建立一个MFC工程,命名为Tao_Music 2.选择为基本对话框 3.包含Windows So ...

随机推荐

  1. linux下keepalived 安装配置

    keepalived是一个类似于layer3, 4 & 7交换机制的软件,也就是我们平时说的第3层.第4层和第7层交换.Keepalived的作用是检测web服务器的状态,如果有一台web服务 ...

  2. PDO控制、连接数据库

    PDO(PHP Data Objects)是一种在PHP里连接数据库的使用接口.PDO与mysqli曾经被建议用来取代原本PHP在用的mysql相关函数,基于数据库使用的安全性,因为后者欠缺对于SQL ...

  3. JVM——深入分析对象的内存布局

    概述 一个对象本身的内在结构需要一种描述方式,这个描述信息是以字节码的方法存储在方法区中的.Class本身就是一个对象,都以KB为单位,如果new Integer()为了表示一个数据就占用KB级别的内 ...

  4. Server push(服务器推送技术)

    一.服务器推送技术Server Push详解:        推送技术Server Push的基础思想是将浏览器主动查询信息改为服务器主动发送信息.服务器发送一批数据,浏览器显示这些数据,同时保证与服 ...

  5. mongoDB & Nodejs 访问mongoDB (一)

    最近的毕设需要用到mongoDB数据库,又把它拿出来再学一学,下盘并不是很稳,所以做一些笔记,不然又忘啦. 安装 mongoDB & mongoVUE mongoDB: https://www ...

  6. 单源最短路径问题之dijkstra算法

    欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1. 算法的原理 以源点开始,以源点相连的顶点作为向外延伸的顶点,在所有这些向外延伸的顶 ...

  7. 深入理解java虚拟机之——JVM垃圾回收策略总结

    如何判断一个对象是否存活 引用计数算法:给对象中添加一个引用计数器,每当有引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器为0的对象就是不可能再被使用.  Java虚拟机里面没有 ...

  8. php错误的处理

    错误的分类 通常分3种: 语法错误: 程序运行之前,都要先检查语法.如果语法有错误,就会立即报错,并且不会去执行程序. 运行时错误: 就是在程序语法检查通过后,,开始运行程序并在此过程中遇到的错误.常 ...

  9. github多用户git push错误remote: Permission to user1/z.git denied to user2

    背景:同一台电脑的public key同时添加到了github的两个账户,导致user1的仓库没法正常提交. 解决办法:为两个账户分别配置ssh key,配置~/.ssh/config文件(windo ...

  10. rsync (windows 服务端,linux客户端)将windows上的数据同步到linux服务器,反之也可

    一:总体概述. 1.windows上面首先装CW_rsync_Server.4.1.0_installer,安装时要输入的用户名密码要记住哦!接下来就是找到rsyncd.conf进入配置细节 2.li ...