expo-video实现横屏播放

我在使用expo-video的过程中,发现它的全屏功能很是差强人意,所以想自己二次封装一下
如果看过我之前的expo文章,应该可以轻松的读懂下面的代码

一、安装相关依赖

npx expo install expo-video expo-router expo-screen-orientation expo-navigation-bar react-native-safe-area-context
依赖包 功能描述
expo-video 一个视频播放器
expo-router 路由
expo-screen-orientation 控制屏幕方向
expo-navigation-bar Android设备的导航设置
expo-device 获取设备类型
react-native-safe-area-context 获取设备的安全区域尺寸

二、主要代码

import {Image, TouchableOpacity, View, Text, Platform, TouchableWithoutFeedback} from 'react-native';
import {useVideoPlayer, VideoView} from 'expo-video';
import {useSafeAreaFrame} from 'react-native-safe-area-context'
import {useEffect, useState} from "react";
import {setStatusBarHidden} from "expo-status-bar";
import * as NavigationBar from 'expo-navigation-bar'
import * as ScreenOrientation from "expo-screen-orientation";
import {useNavigation} from "expo-router";
import fullScreenExit from '@/icons/fullscreen-exit.png'
import fullScreenEnter from '@/icons/fullScreen-enter.png'
import play from '@/icons/play.png'
import pause from '@/icons/pause.png'
import {useEvent, useEventListener} from "expo";export default function MyVideo({videoSource}) {const navigation = useNavigation()// 当前屏幕或窗口的可视区域尺寸const {width, height} = useSafeAreaFrame()// 全屏状态管理const [isFullscreen, setIsFullscreen] = useState(false);// 是否显示自定义控件const [showCustomControls, setShowCustomControls] = useState(true);// 当前视频总时长const [duration, setDuration] = useState(0);// 当前视频已播时长const [position, setPosition] = useState(0)// 快进/后退时间(秒)const SEEK_TIME = 15;// 播放器状态,idle errorconst [status, setStatus] = useState('idle')const player = useVideoPlayer(videoSource, player => {player.loop = false;player.timeUpdateEventInterval = 1player.play()});// 控制自定义视频控制组件的显示或隐藏const handleVideoPress = () => {if ('play' === status) {setShowCustomControls(!showCustomControls);}};useEffect(() => {}, [isFullscreen])// 全屏切换const toFullscreen = async () => {if (isFullscreen) {await exitFullscreen()} else {await enterFullscreen()}}// 全屏const enterFullscreen = async () => {// 隐藏状态栏,使用淡入淡出效果setStatusBarHidden(true, 'fade');// 隐藏蒙版setShowCustomControls(false)// 隐藏页面路由的顶部导航栏navigation.setOptions({headerShow: false})// 在Android平台上隐藏导航栏Platform.OS === 'android' && (await NavigationBar.setVisibilityAsync('hidden'));// 全屏标志setIsFullscreen(true);// 锁定屏幕方向为横向await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);};// 退出全屏const exitFullscreen = async () => {setStatusBarHidden(false, 'fade')setShowCustomControls(false)navigation.setOptions({headerShow: true})Platform.OS === 'android' && (await NavigationBar.setVisibilityAsync('visible'))setIsFullscreen(false)await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP)};// 后退const rewind = async () => {player.seekBy(-SEEK_TIME)};// 快进const fastForward = async () => {player.seekBy(SEEK_TIME)};// 视频播放或暂停const togglePlayPause = async () => {if (player.status !== 'readyToPlay') {return}if (player.playing) {player.pause()} else {player.play()}}// 更精确的点击处理const handleProgressBarPress = async (event) => {if (duration === 0) return;const {locationX} = event.nativeEvent;const progressPercentage = locationX / (width - 50);const targetPosition = progressPercentage * duration;await player.seekBy(targetPosition - position)}// 视频状态监听useEventListener(player, 'statusChange', ({oldStatus, status: newStatus}) => {setDuration(player.duration)if ('idle' === oldStatus) {// 初次加载时setStatus('idle')} else if ('error' === newStatus) {// 加载错误时setStatus('error')setShowCustomControls(true)} else {// 加载成功时setStatus('play')if ('idle' === status) {setShowCustomControls(false)}}})// 视频时间变化监听useEventListener(player, 'timeUpdate', (event) => {const num = Math.floor(event.currentTime)setPosition(num)});// 监听播放状态,true or falseconst {isPlaying} = useEvent(player, 'playingChange', {isPlaying: player.playing})// 格式化进度时间显示const formatTime = (seconds) => {if (Number.isNaN(seconds)) {return '0:00'}const mins = Math.floor(seconds / 60);const secs = Math.floor(seconds % 60);return `${mins}:${secs < 10 ? '0' : ''}${secs}`;};return (<TouchableOpacityactiveOpacity={1}onPressOut={handleVideoPress}className={isFullscreen ? '' : 'mt-10'}><VideoView style={{width: width,height: isFullscreen ? height : width / 16 * 9,backgroundColor: 'black',}}player={player}nativeControls={false}allowsPictureInPicture/>{showCustomControls &&<View className='absolute inset-0 flex flex-col justify-between bg-black/50'>{'idle' === status || 'error' === status ?<><Imagesource={require('@/images/poster.png')}className='absolute inset-0 w-full h-full'resizeMode="cover"/><View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}><TextclassName={'error' !== status ? 'color-green-500' : 'color-red-500'}>{'error' !== status ? '加载中...' : '加载失败'}</Text></View></>:<><View className='h-1/6 justify-around items-center'><Text className="text-white text-lg font-bold">{formatTime(position)} / {formatTime(duration)}</Text></View><View className='flex-row justify-around items-center'><TouchableOpacityclassName='p-1 bg-white/30 rounded-lg'onPress={rewind}><Image source={require('@/icons/left.png')}style={{width: 25, height: 25}}tintColor='#FFFFFF'contentFit="contain"/></TouchableOpacity><TouchableOpacityclassName='p-1 bg-white/30 rounded-lg'onPress={togglePlayPause}><Imagesource={isPlaying ? pause : play}style={{width: 25, height: 25}}tintColor='#FFFFFF'contentFit="contain"/></TouchableOpacity><TouchableOpacityclassName='p-1 bg-white/30 rounded-lg'onPress={fastForward}><Image source={require('@/icons/right.png')}style={{width: 25, height: 25}}tintColor='#FFFFFF'contentFit="contain"/></TouchableOpacity></View><View className='flex-row justify-center items-center h-1/6'>{/* 进度条 */}<TouchableWithoutFeedbackonPress={handleProgressBarPress}><View className='p-2.5 flex-1'><View className='h-1 bg-gray-300 rounded-sm'><ViewclassName='h-full bg-[#f8509c] rounded-sm'style={{width: (position === 0 ? '0%' : `${(position / duration) * 100}%`)}}/></View></View></TouchableWithoutFeedback>{/*全屏控制按钮*/}<View style={{paddingRight: 5}}><TouchableOpacityclassName='p-0 bg-white/0 rounded-lg'onPress={toFullscreen}><Image source={isFullscreen ? fullScreenExit : fullScreenEnter}style={{width: 20, height: 20}}tintColor='#FFFFFF'contentFit="contain"/></TouchableOpacity></View></View></>}</View>}</TouchableOpacity>);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1196412.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2026百度AI优化GEO服务商 TOP5:综合实力强的文心一言GEO服务商领跑AI搜索破局赛道

综合实力强的 GEO服务商 赋能品牌实现 百度AI 精准曝光与流量转化 随着生成式人工智能的迅速发展,AI搜索平台正在重构传统信息分发与获取的模式。特别是以百度“文心一言”为代表的AI原生平台,逐步替代了传统的搜索入…

【珍藏干货】从零开始实战:企业级AI售前机器人开发全流程(含三大核心系统详解)

“从0到1完成一个企业级AI售前机器人的实战指南。” AI应用中除了我们常见的工作流、各类功能节点之外&#xff0c;通常还会附带有三个辅助系统&#xff1a; 用来AI记住用户的历史交互信息&#xff0c;从而提供更连贯、个性化和高效的响应的记忆系统用来验证AI在实际应用中的…

盘点7款热门AI论文写作助手:提升学术效率并有效减少重复内容

AI写论文工具排名&#xff1a;7大模型查重率低技巧推荐 7大AI论文工具核心对比 工具名称 核心功能 查重优化 适用场景 效率评分 AiBiye 论文全流程辅助 智能降重 从选题到定稿 ★★★★★ AiCheck 查重与降重 深度降重算法 论文修改阶段 ★★★★☆ AskPaper …

想让win11暂停系统自动更新要怎么办?如何彻底禁止win11系统自动更新

Windows 11的自动更新功能&#xff0c;初衷是为了提升系统安全与性能。但在实际使用中&#xff0c;频繁的后台下载和强制重启往往让人困扰&#xff0c;尤其是对正在工作、游戏或需要稳定环境的用户来说&#xff0c;更是不可忽视的问题。因此&#xff0c;学会灵活地关闭或延迟自…

AI论文助手Top8:全方位对比主流平台写作水准与降重能力,高效解决用户需求

AI论文生成工具排行榜&#xff1a;8个网站对比&#xff0c;论文降重写作功能全 工具对比总结 以下是8个AI论文工具的简要排名&#xff0c;基于核心功能、处理速度和适用性对比。排名侧重实用性与用户反馈&#xff0c;数据源于引用内容案例&#xff1a; 工具名称 主要功能 优…

Leetcode 238. Product of Array Except Self

问题理解 给定一个整数数组 nums,要求返回一个新数组 answer,其中 answer[i] 是除 nums[i] 以外所有元素的乘积。注意,有0存在。思路 第一个想法是类似sliding window法,先算第 1~(n-1) 个元素的乘积,作为ans[0],…

电脑win11系统怎么关闭自动更新,win11暂停和关闭系统自动更新6大招轻松搞定

Windows 11的自动更新功能&#xff0c;初衷是为了提升系统安全与性能。但在实际使用中&#xff0c;频繁的后台下载和强制重启往往让人困扰&#xff0c;尤其是对正在工作、游戏或需要稳定环境的用户来说&#xff0c;更是不可忽视的问题。因此&#xff0c;学会灵活地关闭或延迟自…

Oracle数据库迁移至KingbaseES:完整实战指南

Oracle数据库迁移至KingbaseES&#xff1a;完整实战指南 KingbaseES内部提供了大量的Oracle兼容特性&#xff0c;因此&#xff0c;在实际的迁移过程中&#xff0c;一般只需对导出脚本做很少的修改&#xff0c;甚至在迁移对象功能全兼容的情况下可以不做任何修改。不仅如此&…

【异常】FreeMarker 模板文件找不到问题解决方案 Template not found for name “mail/captcha.ftl“.

一、报错内容 cn.hutool.core.io.IORuntimeException: TemplateNotFoundException: Template not found for name "mail/captcha.ftl". The name was interpreted by this TemplateLoader: ClassTemplateLoader(classLoader="jdk.internal.loader.ClassLoaders…

保姆级教程:Ubuntu搭建本地AI助手,从Docker到DeepSeek-R1一站式指南,建议收藏!

本文主要介绍如何在Ubuntu操作系统环境下&#xff0c;零基础快速安装Docker环境、安装Ollama、安装本地大模型DeepSeek-R1和大模型可视化工具Open WebUI&#xff0c;快速在本地搭建一款专属AI智能小助手。 1.检查Docker环境 检查是否已安装docker运行环境&#xff0c;在控制台…

NAS —— Centos8挂载Nas到本地目录使用(多图完整教程)

需求与结果 需求:有一台Centos8台式机、一台Synology Nas(DS821),需要将Nas挂载到Centos8上目录上进行使用。 最终结果: 步骤 1、首先保证Centos8可以ping通NAS设备、同时可在Centos8上访问NAS网页端。        2、进入NAS网页操作 (1) 进入DSM控制面板 → 共享文件…

由特殊到一般

lc3301 从最大元素开始贪心 对数组降序排序后&#xff0c;让每个元素不超过前一个元素减1 若出现非正数则返回-1&#xff0c;否则计算数组元素总和 class Solution { public: long long maximumTotalSum(vector<int>& maximumHeight) { ranges::sort(max…

【AI+教育】实践:从策略优化到群体应用的探索

例如&#xff0c;有研究聚焦智能苏格拉底绘画机器人的开发。该机器人内置苏格拉底对话模式&#xff0c;这是一种古老的对话模式。苏格拉底的对话模式&#xff0c;主张通过不断追问&#xff0c;引导人们对自身所做之事进行更深层次的反思与思考。研究通过苏格拉底追问式构建多智…

深入解析:基于势场法的多智能体机器人编队控制

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

吐血推荐8个AI论文软件,助你轻松搞定本科生毕业论文!

吐血推荐8个AI论文软件&#xff0c;助你轻松搞定本科生毕业论文&#xff01; AI工具让论文写作不再难 对于许多本科生来说&#xff0c;撰写毕业论文是一项既复杂又充满挑战的任务。从选题到资料收集&#xff0c;从大纲搭建到初稿完成&#xff0c;每一个环节都可能让人感到手足无…

揭秘提示工程架构师在电子商务应用的领先策略

揭秘提示工程架构师在电子商务应用的领先策略&#xff1a;开启电商智能新时代 关键词&#xff1a;提示工程架构师、电子商务、语言模型、用户体验、个性化推荐、智能客服 摘要&#xff1a;本文深入探讨提示工程架构师在电子商务领域的关键作用及其领先策略。通过生动的比喻和…

SDL2库基础使用

1.初始化 SDL2 子系统(视频相关),失败则打印错误并返回 -1 if (SDL_Init(SDL_INIT_VIDEO)) {fprintf(stderr, "SDL_Init: %s\n", SDL_GetError()); // 输出初始化失败原因return -1; // 返回错误码 }2.…

android 系统中间件和 平台中间件 的区别,Framework等

这是一个非常专业的问题&#xff0c;涉及到Android系统架构的核心层次。简单来说&#xff0c;平台中间件是通用、标准化的“官方层”&#xff0c;而系统中间件是厂商深度定制、差异化的“优化层”。 下面我通过一个表格来清晰地概括两者的主要区别&#xff0c;然后再详细解释&a…

宝妈宝爸必看!儿童羽绒服十大名牌揭秘

宝妈宝爸必看!儿童羽绒服十大名牌揭秘开篇引入 家人们,冬天的寒风那叫一个凛冽,每次带娃出门,看着孩子被冻得小脸蛋红扑扑的,当家长的真是心疼!给孩子选一件暖和又靠谱的羽绒服,就成了咱当务之急。可市场上儿童…

【Script】加载工程文件

【Script】加载工程文件 引言 正文 调用方法。 加载当前目录下的文件 加载存放在特定目录下的文件 Author: JiJi \textrm{Author: JiJi} Author: JiJi Created Time: 2026.01.21 \textrm{Created Time: 2026.01.21} Created Time: 2026.01.21