從0到1利用React-Native構(gòu)建百思不得姐(四)

如果感覺(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嗤朴、onLoadonProgressonEndonError:關(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)表示了颁湖。

視頻Cell.png

如果用過(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è)類里面的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末裙戏,一起剝皮案震驚了整個(gè)濱河市乘凸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌累榜,老刑警劉巖营勤,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異壹罚,居然都是意外死亡葛作,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門猖凛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赂蠢,“玉大人,你說(shuō)我怎么就攤上這事辨泳∈瘢” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵漠吻,是天一觀的道長(zhǎng)量瓜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)途乃,這世上最難降的妖魔是什么绍傲? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮耍共,結(jié)果婚禮上烫饼,老公的妹妹穿的比我還像新娘。我一直安慰自己试读,他們只是感情好杠纵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钩骇,像睡著了一般比藻。 火紅的嫁衣襯著肌膚如雪铝量。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天银亲,我揣著相機(jī)與錄音慢叨,去河邊找鬼。 笑死务蝠,一個(gè)胖子當(dāng)著我的面吹牛拍谐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播馏段,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼轩拨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了院喜?” 一聲冷哼從身側(cè)響起亡蓉,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎够坐,沒(méi)想到半個(gè)月后寸宵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡元咙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巫员。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庶香。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖简识,靈堂內(nèi)的尸體忽然破棺而出赶掖,到底是詐尸還是另有隱情,我是刑警寧澤七扰,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布奢赂,位于F島的核電站,受9級(jí)特大地震影響颈走,放射性物質(zhì)發(fā)生泄漏膳灶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一立由、第九天 我趴在偏房一處隱蔽的房頂上張望轧钓。 院中可真熱鬧,春花似錦锐膜、人聲如沸毕箍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)而柑。三九已至文捶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間媒咳,已是汗流浹背拄轻。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伟葫,地道東北人恨搓。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像筏养,于是被迫代替她去往敵國(guó)和親斧抱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容