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

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

如果遇到什么问题可以在评论区回复,或者加QQ群397885169讨论

前言

通过前三篇文章,已经完成了百思不得姐中部分功能,包括列表滚动,四种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:要实现视频播放的重要组件啦!这个实现类似于网页的<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来实现的。

<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}
    // 重复播放
    repeat={false}
    onLoadStart={this.onLoadStart}
    onLoad={this.onLoad}
    onProgress={this.onProgress}
    onEnd={this.onEnd}
    onError={this.onError}
    ref="videoPlayer"
/>

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

使用

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(
            <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}
                        // 重复播放
                        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
    },
});

总结

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容