react-native-video实现全屏播放

react-native-video 有全屏播放的方法 presentFullscreenPlayer,但是使用的时候发现在安卓上全屏方法不好使,虽然可以人为的控制播放窗口大小为全屏,视频是能播放了,但发现没有可以控制播放的组件(暂停/播放按钮,进度条等)。仔细看文档发现有这段说明:

Put the player in fullscreen mode.
On iOS, this displays the video in a fullscreen view controller with controls.
On Android ExoPlayer & MediaPlayer, this puts the navigation controls in fullscreen mode. It is not a complete fullscreen implementation, so you will still need to apply a style that makes the width and height match your screen dimensions to get a fullscreen video.

尝试了好久还是搞不定,索性就自己定制播放的控制组件。

为了更好的体验,我决定把播放器做成一个单独全屏播放页面,当点击播放视频的时候,直接跳转到播放器页面,页面上有关闭按钮,而且安卓上播放页面点击回退可以返回刚刚的页面,比较符合逻辑。之前我把跳转播放前的页面和播放组件放同一个页面,点击播放时,设置播放组件全屏可见并把其他内容藏起来。但是这样做有些复杂,而且很难隐藏页面的状态栏和标题栏,点击返回时会回退到上一个页面,可能用户此时只是想退出播放,回到播放前的页面。

播放和暂停功能比较容易,图标可以使用 react-native-vector-icons 上的图标。因为项目使用的是 antd-mobile-rn 的 UI 库,所以播放进度条可以直接使用现成的组件 Slider,拖动进度条时根据进度条位置重新设置播放进度可以简单的实现播放进度控制(this.player.seek(value)),这里用 this.state.paused 控制视频的播放和暂停。

关于横屏播放,项目暂时没有这个需求,为节省时间,这里也不深究,可以参考一下这里。最后展示一下效果

react-native-video1.png
react-native-video1.pn2.png

详细代码如下:

import * as React from "react";
import {
  View,
  Text,
  StatusBar,
  SafeAreaView,
  Platform,
  TouchableOpacity,
  ViewStyle,
  TextStyle,
  StyleSheet
} from "react-native";
import { Toast, ActivityIndicator, Slider } from "antd-mobile-rn";
import MaterialsIcon from "react-native-vector-icons/MaterialIcons";
import Video from "react-native-video";
import * as _ from "lodash";
import { size, color } from "../../config";

export default class VideoPlayer extends React.Component<any, any> {
  static navigationOptions = ({ navigation }: any) => ({
    header: null
  });

  player: any;
  constructor(props: IVideoPlayerProps) {
    super(props);
    this.state = {
      onLoading: false,
      videoHeight: size.height,
      playerIconVisible: true,
      paused: true,
      playableDuration: 0,
      currentTime: 0,
      progressPercent: 0
    };
  }

  render() {
    // videoSource: { uri: 'http://cdn.xxx.com/assets/videos/xxx.mp4' } OR require('../../local.mp4')
    const videoSource = _.get(this.props.navigation, "state.params.source");
    let sourceType = _.get(
      this.props.navigation,
      "state.params.sourceType",
      "url"
    );
    if (!videoSource || (sourceType === "url" && !videoSource.uri)) {
      Toast.fail("视频路径不正确!", 2);
      return <SafeAreaView style={indicatorStyles.container} />;
    }
    return (
      <SafeAreaView style={indicatorStyles.container}>
        <Video
          style={{ width: size.width, height: this.state.videoHeight }}
          source={videoSource}
          ref={(ref: any) => (this.player = ref)}
          paused={this.state.paused}
          onLoadStart={(e: any) => {
            console.log("onLoadStart: ", e);
            this.setState({ onLoading: true });
          }}
          onEnd={() =>
            this.setState({ playerIconVisible: true, onLoading: false })
          }
          onError={() => {
            Toast.fail("加载视频出错", 2);
            this.setState({ onLoading: false });
          }}
          onLoad={(e: any) => {
            console.log("onLoad && ready to play: ", e);
            this.setState({ paused: true, onLoading: false });
            const height = _.get(e, "naturalSize.height");
            const width = _.get(e, "naturalSize.width");
            console.log("video' width &&  height: ", width, height);
            let videoHeight = this.state.videoHeight;
            if (_.isNumber(height) && _.isNumber(width)) {
              videoHeight = (size.width * height) / width;
            }
            this.setState({
              playableDuration: _.get(e, "duration", 0),
              videoHeight: videoHeight,
              paused: false
            });
          }}
          onProgress={(e: any) => {
            // console.log('onProgress: ', e);
            let currentTime = _.get(e, "currentTime", 0);
            let playableDuration = _.get(e, "playableDuration", 1);
            this.setState({
              currentTime,
              progressPercent: (currentTime / playableDuration) * 100
            });
          }}
          onFullscreenPlayerDidPresent={() => {
            this.setState({ paused: false });
          }}
          onFullscreenPlayerWillDismiss={() => {
            this.setState({ paused: true });
          }}
        />

        <TouchableOpacity
          style={indicatorStyles.videoCover}
          onPress={async () => {
            await this.setState((prevState: any) => ({
              playerIconVisible: !prevState.playerIconVisible
            }));
            if (!this.state.paused && this.state.playerIconVisible) {
              setTimeout(() => {
                this.setState({ playerIconVisible: false });
              }, 3000);
            }
          }}
        >
          {this.state.onLoading && <ActivityIndicator color="#fff" />}

          <TouchableOpacity
            style={{
              position: "absolute",
              top: Platform.OS === "ios" ? 0 : StatusBar.currentHeight,
              right: 0,
              padding: 10
            }}
            onPress={() => this.props.navigation.goBack()}
          >
            <MaterialsIcon
              name="close"
              size={30}
              color={"rgba(255, 255, 255, 0.8)"}
            />
          </TouchableOpacity>

          {this.state.playerIconVisible && (
            <View style={indicatorStyles.playerController}>
              <TouchableOpacity
                style={indicatorStyles.playerIcon}
                onPress={() => {
                  this.setState((prevState: any) => ({
                    paused: !prevState.paused
                  }));
                }}
              >
                <MaterialsIcon
                  name={this.state.paused ? "play-arrow" : "pause"}
                  size={30}
                  color={"rgba(255, 255, 255, 0.8)"}
                />
              </TouchableOpacity>
              <Text style={indicatorStyles.playerTime}>
                {this.state.currentTime}
              </Text>
              <View style={indicatorStyles.playerProgress}>
                <Slider
                  value={this.state.currentTime}
                  min={0}
                  max={this.state.playableDuration}
                  onChange={(value: number) => {
                    this.player.seek(value);
                    this.setState({ paused: false });
                  }}
                  onAfterChange={(value: number) =>
                    console.log("afterChange: ", value)
                  }
                />
              </View>
              <Text style={indicatorStyles.playerTime}>
                {this.state.playableDuration}
              </Text>
            </View>
          )}
          <View />
        </TouchableOpacity>
      </SafeAreaView>
    );
  }
}

const PLAYER_ICON_WIDTH = 30;
const PLAYER_TIME = 50;
const PROGRESS_WIDTH = size.width - PLAYER_ICON_WIDTH - PLAYER_TIME * 2 - 10;
const style = {
  container: {
    flex: 1,
    backgroundColor: "#000",
    alignItems: "center",
    justifyContent: "center"
  },
  videoCover: {
    position: "absolute",
    width: "100%",
    height: "100%",
    backgroundColor: "transparent",
    alignItems: "center",
    justifyContent: "center"
  },
  playerController: {
    flexDirection: "row",
    position: "absolute",
    bottom: 5,
    left: 0,
    width: "100%",
    backgroundColor: "rgba(0,0,0,0.3)",
    alignItems: "center"
  },
  playerIcon: {
    width: PLAYER_ICON_WIDTH,
    height: 30,
    marginLeft: 10,
    justifyContent: "center",
    alignItems: "center"
  },
  playerProgress: {
    justifyContent: "center",
    height: 4,
    width: PROGRESS_WIDTH,
    flex: 1
  },
  playerTime: {
    width: PLAYER_TIME,
    height: 30,
    color: "#fff",
    padding: 5,
    textAlign: "center"
  }
};
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,967评论 3 119
  • 躺在床上在感叹,都不知道今天怎么过来的,一天又过去了。 室友在一边打趣我说,你记得哪天是怎么过来的? 于是,炮轰开...
    zerooo阅读 291评论 6 2
  • 她说有的人变成了学霸有的人变成了文艺青年有的人变成了土豪有的人变成了男神女神而又有的人变成了段子狗 。那个现在是学...
    圣诞老人叫瓦斯哥阅读 1,717评论 0 4
  • 春秋之社会,周礼衰败,社稷动摇。始有诸子立说,百家兴起。探天下动乱之根源,解庶民于水深火热之中。 ...
    陳年风流阅读 749评论 3 2
  • 最近室友老是抱怨自己怎么还没有男朋友怎么还是单身狗一枚。我也有同样的困惑,到底是什么导致了我们毕业...
    旅途在前阅读 235评论 0 0