如果感覺(jué)我寫的不錯(cuò),關(guān)注我浸颓,給個(gè)star哦兽叮!項(xiàng)目地址
如果遇到什么問(wèn)題可以在評(píng)論區(qū)回復(fù)鹦聪,或者加QQ群397885169討論
前言
通過(guò)前三篇文章,已經(jīng)完成了百思不得姐中部分功能规丽,包括列表滾動(dòng),四種Cell的自定義艘狭,gif圖,長(zhǎng)圖的查看等,但視頻播放和聲音播放還沒(méi)有完成似谁,這篇文章主要寫一下視頻播放
吐槽
在創(chuàng)建這個(gè)項(xiàng)目的時(shí)候react-native的版本號(hào)0.38.1龙致,前期用的時(shí)候是沒(méi)有太大問(wèn)題的(除了各種各樣的警告),但在做視頻的時(shí)候發(fā)現(xiàn),視頻的一些方法竟然不走霜大!致使我將版本號(hào)降低到了0.35.0,但又遇到了底部TabBar布局出現(xiàn)了問(wèn)題途茫,margin方法竟然不能對(duì)Tabbar起作用错沃。這TM就很尷尬了??????,沒(méi)辦法為了頁(yè)面效果司浪,只能將版本再升級(jí)回去,接下來(lái)就更悲劇了,升級(jí)到0.39.1項(xiàng)目竟然無(wú)法運(yùn)行?汛埂!页慷!無(wú)法運(yùn)行。。郎哭。翻來(lái)覆去糾結(jié)了一天依鸥,最終只能用0.38的版本暫時(shí)處理這個(gè)問(wèn)題抬闯,待到時(shí)機(jī)成熟睡榆,再更新react-native版本吧。
0.38.1
中的問(wèn)題:
1.paused
:暫停/播放功能失效,只能播放奖蔓。因?yàn)椴荒軙和#栽谶\(yùn)行Demo的時(shí)候,觀看視頻的時(shí)候只能看完或者刷新頁(yè)面知允。
2.onLoadStart
嗤朴、onLoad
、onProgress
、onEnd
、onError
:關(guān)于視頻加載,播放,進(jìn)度,結(jié)束餐禁,加載錯(cuò)誤的方法都失效了末盔。所以游盲,項(xiàng)目里面的進(jìn)度處理是通過(guò)計(jì)時(shí)器做了假的判斷匕得。
導(dǎo)入組件
react-native-video
:要實(shí)現(xiàn)視頻播放的重要組件啦!這個(gè)實(shí)現(xiàn)類似于網(wǎng)頁(yè)的<Video />
標(biāo)簽役听,主要調(diào)用的是原生的東西瘤袖,所以需要使用react-native link
來(lái)link一下原生的東西既琴。
規(guī)劃
在真正搭建頁(yè)面之前需要做一個(gè)簡(jiǎn)單的規(guī)劃松靡,我本來(lái)想用圖來(lái)表示的,沒(méi)苦于沒(méi)有時(shí)間浦旱,在這里只能用一些截圖來(lái)表示了颁湖。
如果用過(guò)百思不得姐這個(gè)app,那應(yīng)該知道這個(gè)視頻是可以在Cell上播放的例隆,我第一感覺(jué)也是要實(shí)現(xiàn)這個(gè)效果甥捺,最終實(shí)現(xiàn)了,但也埋下了一個(gè)深坑镀层。
百思不得姐的接口里面是提供了視頻的占位圖镰禾,播放數(shù)和時(shí)間的,除了時(shí)間之外其他的數(shù)據(jù)都可以直接使用,唯一要處理的就是那個(gè)時(shí)間吴侦。
videotime:74
屋休,這個(gè)是接口中提供的數(shù)據(jù),因?yàn)槭侨繒r(shí)間备韧,所以這里只需要簡(jiǎn)單的格式化一下就行了劫樟。
// 初始化一個(gè)字符,用來(lái)承接剩余的時(shí)間
let videoTime = this.props.videoData.videotime / 60;
// 因?yàn)閖s里面如果直接調(diào)用toFixed方法會(huì)自動(dòng)四舍五入盯蝴,所以在這里先保存一位小數(shù)毅哗,再用下面的方法將這個(gè)小數(shù)去掉听怕。
videoTime = videoTime.toFixed(1);
videoTime = videoTime.substring(0,videoTime.lastIndexOf('.'));
// 接下來(lái)的就是用來(lái)判斷時(shí)間的各種狀態(tài)了捧挺,大于0小于60的再時(shí)間前面加個(gè)0;分如果沒(méi)到1尿瞭,那就顯示00闽烙。
let videoLastTime = this.props.videoData.videotime % 60;
if (videoTime == 0){
videoTime = '0' + videoTime;
}
if (videoTime >= 1 && videoTime <= 9){
videoTime = '0' + videoTime;
}
if (videoLastTime<=9 && videoLastTime>=0){
videoLastTime = '0' + videoLastTime;
}
占位圖中間需要一個(gè)按鈕,左下角和右下角需要展示播放數(shù)和視頻時(shí)間声搁,第一眼看過(guò)去黑竞,是不是應(yīng)該想到要用絕對(duì)定位實(shí)現(xiàn)?
![Uploading 視頻播放_(tái)838354.png . . .]
上面這張圖是實(shí)現(xiàn)視頻播放疏旨,我真的是放在了Cell上播放哦很魂。我在最后會(huì)說(shuō)遇到的坑有多深。
視頻播放用的是上面引入的react-native-video
來(lái)實(shí)現(xiàn)的檐涝。
<Video
source={{uri:this.props.videoData.videouri}}
style={{width:width-20,height:this.state.imageHeight}}
// 速率
rate={this.state.rate}
// 開始暫停
paused={false}
// 聲音大小
volume={this.state.volume}
// 靜音
muted={this.state.muted}
// 屏幕
resizeMode={this.state.resizeMode}
// 重復(fù)播放
repeat={false}
onLoadStart={this.onLoadStart}
onLoad={this.onLoad}
onProgress={this.onProgress}
onEnd={this.onEnd}
onError={this.onError}
ref="videoPlayer"
/>
底部還有一個(gè)工具條遏匆,里面放著播放/暫停按鈕,播放時(shí)間谁榜,視頻時(shí)間幅聘,靜音,全屏這些東西窃植,而且這個(gè)工具跳是放在了視頻上帝蒿,點(diǎn)擊的視頻的時(shí)候會(huì)隱藏和展示。這些都是需要自定制一下的巷怜。
使用
videoItem.js
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component} from "react";
import {
AppRegistry,
StyleSheet,
Text,
View,
Platform,
AlertIOS,
ActivityIndicator,
InteractionManager,
TouchableOpacity,
ListView,
TextInput,
Modal,
PixelRatio
} from "react-native";
import Video from "react-native-video";
import Dimensions from "Dimensions";
const {width, height} = Dimensions.get('window');
import Icon from 'react-native-vector-icons/Ionicons';
import Image from 'react-native-image-progress';
import ProgressBar from 'react-native-progress/Bar';
export default class Detail extends Component {
static defaultProps = {
videoUrl: '',
videoData: React.PropTypes.string,
};
constructor(props){
super(props);
let imageHeight = width * this.props.videoData.height / this.props.videoData.width;
if (imageHeight > height-150){
imageHeight = 300;
}
let videoTime = this.props.videoData.videotime / 60;
videoTime = videoTime.toFixed(1);
videoTime = videoTime.substring(0,videoTime.lastIndexOf('.'));
let videoLastTime = this.props.videoData.videotime % 60;
if (videoTime == 0){
videoTime = '0' + videoTime;
}
if (videoTime >= 1 && videoTime <= 9){
videoTime = '0' + videoTime;
}
if (videoLastTime<=9 && videoLastTime>=0){
videoLastTime = '0' + videoLastTime;
}
this.state = {
imageHeight:imageHeight,
videoTime:videoTime,
videoLastTime:videoLastTime,
videoNormalTime:'00:00',
videoMinTime:'00',
videoSecTime:'00',
rate: 1,
volume: 1,
// 聲音
muted: false,
resizeMode: 'contain',
duration: 0.0,
currentTime: 0.0,
controls: false,
// 暫停
paused: true,
skin: 'custom',
isVideoLoad:false,
// 是否播放
isPlay:true,
// 是否有聲音
isVolume:true,
isVideoOk:false,
//底部ToolBar隱藏
isToolHidden:false,
};
this.onLoadStart = this.onLoadStart.bind(this);
this.onLoad = this.onLoad.bind(this);
this.onProgress = this.onProgress.bind(this);
this.onEnd = this.onEnd.bind(this);
this.onError = this.onError.bind(this);
this.onPlay = this.onPlay.bind(this);
// 暫停播放
this.onPause = this.onPause.bind(this);
// 恢復(fù)播放
this.resumePlayer = this.resumePlayer.bind(this);
}
// 恢復(fù)播放
resumePlayer(){
if (this.state.paused) {
this.setState({
paused:false
})
}
}
// 暫停播放
onPause(){
if (!this.state.paused) {
this.setState({
paused:true
})
}
}
// 重新播放
onPlay(){
this.refs.videoPlayer.seek(0);
// this.refs.videoPlayer.presentFullscreenPlayer()
}
// 開始加載
onLoadStart(){
console.log('onLoadStart');
}
// 正在加載
onLoad(data){
this.setState({duration: data.duration});
}
// 進(jìn)度條
onProgress(data) {
if(!this.state.isVideoLoad){
this.setState({
isVideoLoad:true
});
}
if(!this.state.isPlay){
this.setState({
isPlay:true
});
}
this.setState({currentTime: data.currentTime});
}
// 視頻結(jié)束
onEnd(){
console.log('onEnd');
// 當(dāng)結(jié)束的時(shí)候暫停播放
this.setState({
currentTime:this.state.duration,
isPlay:false,
});
}
// 視頻出錯(cuò)
onError(error){
console.log(error);
this.setState({
isVideoOk:true
});
}
// 進(jìn)度條調(diào)用的方法
getCurrentTimePercentage() {
if (this.state.currentTime > 0) {
return parseFloat(this.state.currentTime) / parseFloat(this.state.duration);
} else {
return 0;
}
}
// 關(guān)閉定時(shí)器
stopTimer(){
this.setIntervar && clearInterval(this.setIntervar);
}
// 開啟定時(shí)器
startBegin(){
// 1.添加定時(shí)器
this.setIntervar = setInterval(()=>{
let secTime = ++this.state.videoSecTime;
// console.log(this.state.videoMinTime);
let minTime = this.state.videoMinTime;
if(secTime < 10){
secTime = '0' + secTime;
}
if (secTime > 59){
minTime++;
if (minTime < 9){
minTime = '0' + minTime++;
} else {
minTime = minTime++;
}
secTime = '00';
}
let videoTime = minTime + ':' + secTime;
let videoAllTime = this.state.videoTime + ':' + this.state.videoLastTime;
if (videoTime == videoAllTime){
this.stopTimer();
}
//2.3更新?tīng)顟B(tài)機(jī)
this.setState({
videoSecTime:secTime,
videoMinTime:minTime,
});
},1000);
}
// 視頻全屏
renderScreen(){
this.refs.videoPlayer.presentFullscreenPlayer();
}
// 打開聲音
renderYESVolume(){
console.log(this.state.volume);
if (!this.state.volume) {
this.setState({
isVolume:true,
volume:1,
})
}
}
// 關(guān)閉聲音
renderNOVolume(){
console.log(this.state.volume);
if (this.state.volume) {
this.setState({
isVolume:false,
volume:0,
})
}
}
// 恢復(fù)播放
renderPlay(){
console.log(this.state.paused);
if (this.state.paused) {
this.setState({
paused:false,
})
}
}
// 暫停播放
renderPause(){
console.log(this.state.paused);
if (!this.state.paused) {
this.setState({
paused:true,
})
}
}
renderVideoViewPress(){
this.setState({
isToolHidden:!this.state.isToolHidden,
})
}
renderPlayVideo(){
return(
<View style={{backgroundColor:'black'}}>
<TouchableOpacity style={styles.videoViewStyle} onPress={()=>this.renderVideoViewPress()} activeOpacity={1}>
<Video
source={{uri:this.props.videoData.videouri}}
style={{width:width-20,height:this.state.imageHeight}}
// 速率
rate={this.state.rate}
// 開始暫停
paused={false}
// 聲音大小
volume={this.state.volume}
// 靜音
muted={this.state.muted}
// 屏幕
resizeMode={this.state.resizeMode}
// 重復(fù)播放
repeat={false}
onLoadStart={this.onLoadStart}
onLoad={this.onLoad}
onProgress={this.onProgress}
onEnd={this.onEnd}
onError={this.onError}
ref="videoPlayer"
/>
</TouchableOpacity>
{
this.state.isToolHidden ?
<View style={styles.videoToolBarViewStyle}>
{this.state.paused ?
<Icon
name="ios-pause"
size={25}
color='white'
style={styles.videoToolPlayStyle}
onPress={()=>this.renderPlay()}
/>
:
<Icon
name="ios-play"
size={25}
color='white'
style={styles.videoToolPlayStyle}
onPress={()=>this.renderPause()}
/>
}
<View style={styles.videoToolTextViewStyle}>
<Text
style={styles.videoToolTextStyle}>{this.state.videoMinTime + ':' + this.state.videoSecTime}</Text>
<Text
style={styles.videoToolTextStyle}>{this.state.videoTime + ':' + this.state.videoLastTime}</Text>
</View>
{this.state.isVolume ?
<Icon
name="ios-volume-up"
size={25}
color='white'
style={styles.videoVolumeStyle}
onPress={()=>this.renderNOVolume()}
/>
:
<Icon
name="ios-volume-off"
size={25}
color='white'
style={styles.videoVolumeStyle}
onPress={()=>this.renderYESVolume()}
/>
}
<Icon
name="ios-expand"
size={25}
color='white'
style={styles.videoToolExpandStyle}
onPress={()=>this.renderScreen()}
/>
</View>
: null
}
</View>
)
}
renderVideo(){
return(
<Image source={{uri:this.props.videoData.cdn_img}} style={{width:width-20,height:this.state.imageHeight}}
indicator={ProgressBar} resizeMode='contain'>
<Icon
name="ios-play"
size={45}
color='white'
style={[styles.playStyle,{position:'absolute',top:this.state.imageHeight/2-20,left:width/2-30,
}]}
/>
<View style={styles.videoBottomStyle}>
<Text style={styles.videoPlayStyle}>{this.props.videoData.playcount + '播放'}</Text>
<Text style={styles.videoTimeStyle}>{this.state.videoTime + ':' + this.state.videoLastTime}</Text>
</View>
</Image>
)
}
videoPress(){
this.startBegin();
this.setState({
isPlay:false,
paused:false,
})
}
render() {
return (
this.state.isPlay ?
<TouchableOpacity activeOpacity={1} style={styles.videoViewStyle} onPress={()=>this.videoPress()}>
{this.renderVideo()}
</TouchableOpacity>
:
this.renderPlayVideo()
);
}
}
const styles = StyleSheet.create({
videoBottomStyle:{
flexDirection:'row',
position:'absolute',
bottom:5,
width:width-20,
},
videoPlayStyle:{
position:'relative',
left:0,
backgroundColor:'rgba(88, 87, 86, 0.6)',
color:'white',
},
videoTimeStyle:{
position:'absolute',
right:0,
backgroundColor:'rgba(88, 87, 86, 0.6)',
color:'white'
},
playStyle:{
height:60,
width:60,
backgroundColor:'transparent',
borderColor:'white',
borderWidth:2,
borderRadius:30,
paddingTop:8,
paddingLeft:22,
},
videoViewStyle: {
marginBottom:5,
marginLeft:10,
marginRight:10,
},
videoToolBarViewStyle:{
flexDirection:'row',
height:30,
width:width -20,
backgroundColor:'rgba(00, 00, 00, 0.4)',
marginLeft:10,
position:'absolute',
bottom:0
},
videoToolPlayStyle: {
position:'absolute',
left:10,
top:2,
},
videoToolTextViewStyle:{
flexDirection:'row',
height:30,
marginLeft:30
},
videoToolTextStyle:{
marginLeft:10,
fontSize:16,
marginTop:6,
color:'white'
},
videoVolumeStyle:{
position:'absolute',
right:35,
bottom:2
},
videoToolExpandStyle:{
position:'absolute',
top:2,
right:5
},
});
總結(jié)
在state里面會(huì)看到像this.onLoad這類的方法葛超,在0.38.0里面是不能用的,我沒(méi)有刪除是為了以后可能會(huì)找到一個(gè)穩(wěn)定點(diǎn)的版本來(lái)將視頻播放完善延塑。
遇到的坑:用過(guò)react-native的都知道绣张,react-native最大的槽點(diǎn)就是ListView不能復(fù)用,所以页畦,在講視頻播放放在Cell上的時(shí)候胖替,遇到了視頻離開屏幕之后,還是播放的問(wèn)題。這個(gè)問(wèn)題暫時(shí)還沒(méi)有想明白該怎么解決独令,以后有機(jī)會(huì)再深入研究一下端朵。
這篇文章一直想寫的,但最近公司在寫騰訊云燃箭,再加上項(xiàng)目版本的問(wèn)題冲呢,所以就晚了幾天,我項(xiàng)目結(jié)構(gòu)更改了一下招狸,將所有的組件都拆分了出來(lái)敬拓,之前是有一些是放在同一個(gè)類里面的。