使用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. 了解 : prevent default

    基本了解是阻止事件之前设置好的事件触发,像是angular router ui里的 preventDefault是这样的. 在$stateChange的是后,可以调用preventDefault 来阻 ...

  2. Regular Expression(正则表达式)之邮箱验证

    正则表达式(regular expression, 常常缩写为RegExp) 是一种用特殊符号编写的模式,描述一个或多个文本字符串.使用正则表达式匹配文本的模式,这样脚本就可以轻松的识别和操作文本.其 ...

  3. Spark_总结四

    Spark_总结四 1.Spark SQL     Spark SQL 和 Hive on Spark 两者的区别?         spark on hive:hive只是作为元数据存储的角色,解析 ...

  4. Java原生API操作XML

    使用Java操作XML的开源框架比较多,如著名的Dom4J.JDOM等,但个人认为不管你用那个框架都要对JDK原生的API有所了解才能更得心应手的应用.本篇就来简单了解下原生的XML API. JAV ...

  5. vSphere在RedHat6.0上搭建Oracle 11g R2 RAC环境

    一.前期准备工作 1.1 为方便操作,装完系统后我们先安装Vmware Tools: 1.1.1.安装工具 在VMware的菜单栏上选择"虚拟机/安装虚拟机工具(VM/Install VMw ...

  6. Java垃圾回收学习笔记

    通常来说,要写Java代码,你基本上都没必要听说垃圾回收这个概念的.这不,对于已经写了5年多Java代码的我来说,我还没有哪次经历说是需要使用垃圾回收方面的知识来解决问题的.但是,我依然督促自己花了几 ...

  7. [LeetCode]House Robber II (二次dp)

    213. House Robber II     Total Accepted: 24216 Total Submissions: 80632 Difficulty: Medium Note: Thi ...

  8. php线程安全与非线程安全版的区别

    Thread Safe(线程安全)和 None Thread Safe(非线程安全) 背景: Linux/Unix系统采用多进程的工作方式,而Windows系统采用多线程的工作方式. CGI模式是建立 ...

  9. C++标准库之queue(各函数及其使用全)

    原创作品,转载请注明出处:http://www.cnblogs.com/shrimp-can/p/5283520.html 一.FIFO队列,即先入先出队列 1.队列的声明 std::deque< ...

  10. 2015.07.12hadoop伪分布安装

    hadoop伪分布安装   Hadoop2的伪分布安装步骤[使用root用户用户登陆]other进去超级用户拥有最高的权限 1.1(桥接模式)设置静态IP ,,修改配置文件,虚拟机IP192.168. ...