程序员

从0到1利用React-Native构建百思不得姐(四)

如果感觉我写的不错,关注我,给个star哦!项目地址

前言

通过前三篇文章,已经完成了百思不得姐中部分功能,包括列表滚动,四种Cell的自定义,gif图,长图的查看等,但视频播放和声音播放还没有完成,这篇文章主要写一下视频播放

吐槽

在创建这个项目的时候react-native的版本号0.38.1,前期用的时候是没有太大问题的(除了各种各样的警告),但在做视频的时候发现,视频的一些方法竟然不走!致使我将版本号降低到了0.35.0,但又遇到了底部TabBar布局出现了问题,margin方法竟然不能对Tabbar起作用。这TM就很尴尬了🙄🙄🙄,没办法为了页面效果,只能将版本再升级回去,接下来就更悲剧了,升级到0.39.1项目竟然无法运行!!!无法运行。。。翻来覆去纠结了一天,最终只能用0.38的版本暂时处理这个问题,待到时机成熟,再更新react-native版本吧。
0.38.1中的问题:
1.paused:暂停/播放功能失效,只能播放。因为不能暂停,所以在运行Demo的时候,观看视频的时候只能看完或者刷新页面。
2.onLoadStartonLoadonProgressonEndonError:关于视频加载,播放,进度,结束,加载错误的方法都失效了。所以,项目里面的进度处理是通过计时器做了假的判断。

导入组件

react-native-video:要实现视频播放的重要组件啦!这个实现类似于网页的标签,主要调用的是原生的东西,所以需要使用react-native link来link一下原生的东西。

规划

在真正搭建页面之前需要做一个简单的规划,我本来想用图来表示的,没苦于没有时间,在这里只能用一些截图来表示了。

视频Cell.png

如果用过百思不得姐这个app,那应该知道这个视频是可以在Cell上播放的,我第一感觉也是要实现这个效果,最终实现了,但也埋下了一个深坑。
百思不得姐的接口里面是提供了视频的占位图,播放数和时间的,除了时间之外其他的数据都可以直接使用,唯一要处理的就是那个时间。
videotime:74,这个是接口中提供的数据,因为是全部时间,所以这里只需要简单的格式化一下就行了。

// 初始化一个字符,用来承接剩余的时间
let videoTime = this.props.videoData.videotime / 60;
// 因为js里面如果直接调用toFixed方法会自动四舍五入,所以在这里先保存一位小数,再用下面的方法将这个小数去掉。
videoTime = videoTime.toFixed(1);
videoTime = videoTime.substring(0,videoTime.lastIndexOf('.'));
// 接下来的就是用来判断时间的各种状态了,大于0小于60的再时间前面加个0;分如果没到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;
}

占位图中间需要一个按钮,左下角和右下角需要展示播放数和视频时间,第一眼看过去,是不是应该想到要用绝对定位实现?

![Uploading 视频播放_838354.png . . .]

上面这张图是实现视频播放,我真的是放在了Cell上播放哦。我在最后会说遇到的坑有多深。
视频播放用的是上面引入的react-native-video来实现的。

底部还有一个工具条,里面放着播放/暂停按钮,播放时间,视频时间,静音,全屏这些东西,而且这个工具跳是放在了视频上,点击的视频的时候会隐藏和展示。这些都是需要自定制一下的。

使用

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);
        // 恢复播放
        this.resumePlayer = this.resumePlayer.bind(this);
    }
    // 恢复播放
    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});
    }
    // 进度条
    onProgress(data) {

        if(!this.state.isVideoLoad){
            this.setState({
                isVideoLoad:true
            });
        }
        if(!this.state.isPlay){
            this.setState({
                isPlay:true
            });
        }
        this.setState({currentTime: data.currentTime});
    }
    // 视频结束
    onEnd(){
        console.log('onEnd');
        // 当结束的时候暂停播放
        this.setState({
            currentTime:this.state.duration,
            isPlay:false,
        });
    }
    // 视频出错
    onError(error){
        console.log(error);
        this.setState({
            isVideoOk:true
        });
    }
    // 进度条调用的方法
    getCurrentTimePercentage() {
        if (this.state.currentTime > 0) {
            return parseFloat(this.state.currentTime) / parseFloat(this.state.duration);
        } else {
            return 0;
        }
    }

    // 关闭定时器
    stopTimer(){
        this.setIntervar && clearInterval(this.setIntervar);
    }

    // 开启定时器
    startBegin(){
        // 1.添加定时器
        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更新状态机
            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,
            })
        }
    }
    // 关闭声音
    renderNOVolume(){
        console.log(this.state.volume);

        if (this.state.volume) {
            this.setState({
                isVolume:false,
                volume:0,
            })
        }
    }

    // 恢复播放
    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(
            
                this.renderVideoViewPress()} activeOpacity={1}>
                    
                {
                    this.state.isToolHidden ?
                        
                            {this.state.paused ?
                                this.renderPlay()}
                                />
                                :
                                this.renderPause()}
                                />
                            }

                            
                                {this.state.videoMinTime + ':' + this.state.videoSecTime}
                                {this.state.videoTime + ':' + this.state.videoLastTime}
                            

                            {this.state.isVolume ?
                                this.renderNOVolume()}
                                />
                                :
                                this.renderYESVolume()}
                                />
                            }

                            this.renderScreen()}
                            />
                        
                        : null
                }
            
        )
    }

    renderVideo(){
        return(
                
                    
                    
                        {this.props.videoData.playcount + '播放'}
                        {this.state.videoTime + ':' + this.state.videoLastTime}
                    
                
        )
    }

    videoPress(){
        this.startBegin();
        this.setState({
            isPlay:false,
            paused:false,
        })
    }

    render() {
        return (
            this.state.isPlay ?
                this.videoPress()}>
                    {this.renderVideo()}
                
                :
                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
    },
});

总结

在state里面会看到像this.onLoad这类的方法,在0.38.0里面是不能用的,我没有删除是为了以后可能会找到一个稳定点的版本来将视频播放完善。
遇到的坑:用过react-native的都知道,react-native最大的槽点就是ListView不能复用,所以,在讲视频播放放在Cell上的时候,遇到了视频离开屏幕之后,还是播放的问题。这个问题暂时还没有想明白该怎么解决,以后有机会再深入研究一下。

这篇文章一直想写的,但最近公司在写腾讯云,再加上项目版本的问题,所以就晚了几天,我项目结构更改了一下,将所有的组件都拆分了出来,之前是有一些是放在同一个类里面的。