expo-video 自定義控制項(播放暫停、進度條和靜音)

React Native logo
image

下方的播放/暫停按鈕、進度條、音量按鈕都是自定義的。

expo-video 有提供一個 useVideoPlayer hook 來建立 VideoPlayer 實例,可以管理播放器的生命週期,並確保在組件卸載時正確銷毀播放器。(如果需要在元件卸載時不會自動銷毀的 VideoPlayer 可以使用 createVideoPlayer 函數來建立 VideoPlayer)

const player = useVideoPlayer(uri, player => {
  player.loop = true
  player.play()
})

控制靜音

player.muted

const toggleMute = () => {
  player.muted = !isMuted
  setIsMuted(!isMuted)
}

控制播放和暫停

  • 播放:player.play()
  • 暫停:player.pause()
 const togglePlay = () => {
  if (isPlaying) {
    player.pause()
  } else {
    player.play()
  }
  setIsPlaying(!isPlaying)
}

播放進度條

Slider

Slider 使用的是 @react-native-community/slider

<Slider
  style={{ width: 200 }}
  minimumValue={0}
  maximumValue={duration}
  value={currentTime}
  minimumTrackTintColor="#fff"
  maximumTrackTintColor="#2E2E2E"
  thumbTintColor="transparent"
/>

進度控制

  • timeUpdateEventInterval 表示播放器觸發 timeUpdate 事件的間隔(以秒為單位),值為 0 時不會觸發該事件,預設是 0。
  • updateTime 函數用於獲取當前影片播放時間並更新 state
  • expo-video 有自帶Controls,要關閉需要設置 nativeControls={false}
  • Slider 需使用 onValueChangeonSlidingComplete 去更新當前播放時間
    • onValueChange:拖動滑塊時觸發,只更新滑塊在UI上的位置
    • onSlidingComplete:鬆開滑塊時觸發,更新 player.currentTime
const player = useVideoPlayer(uri, player => {
  player.loop = true
  player.play()
  player.timeUpdateEventInterval = 250
})

const [isPlaying, setIsPlaying] = useState(false)
const [isMuted, setIsMuted] = useState(false)
const [currentTime, setCurrentTime] = useState(0)
const [duration, setDuration] = useState(0)

useEffect(() => {
  let animationFrameId: number

  const updateTime = () => {
    if (player) {
      const newTime = player.currentTime
      setCurrentTime(newTime)
      if (typeof player.duration === 'number') {
        setDuration(player.duration)
      }
      if (newTime >= player.duration) {
        player.currentTime = 0
        player.play()
      }
    }
    animationFrameId = requestAnimationFrame(updateTime)
  }

  animationFrameId = requestAnimationFrame(updateTime)
  return () => cancelAnimationFrame(animationFrameId)
}, [player])

  useEffect(() => {
    if (player) {
      setIsPlaying(player.playing)
    }
  }, [player?.playing])

  const togglePlay = () => {
    if (isPlaying) {
      player.pause()
    } else {
      player.play()
    }
    setIsPlaying(!isPlaying)
  }

  const toggleMute = () => {
    player.muted = !isMuted
    setIsMuted(!isMuted)
  }

return (
  <View className={"w-full h-full bg-black justify-center items-center relative"} style={style}>
    <VideoView
      player={player}
      style={{ width: '100%', height: '100%', position: 'absolute', top: 0, left: 0 }}
      nativeControls={false}
      allowsFullscreen={false}
      allowsPictureInPicture={false}
    />
    {/* Controls */}
    <View className="absolute left-0 right-0 bottom-16 pb-4 px-4 bg-transparent">
      <View className="flex-row items-center justify-center gap-2 mb-1">
        <Pressable className="flex-1 items-end" onPress={togglePlay}>
          <FontAwesomeIcon name={isPlaying ? 'pause' : 'play'} size={28} color="#fff" />
        </Pressable>
        <Slider
          style={{ width: width - 100 }}
          minimumValue={0}
          maximumValue={duration}
          value={currentTime}
          minimumTrackTintColor="#fff"
          maximumTrackTintColor="#2E2E2E"
          thumbTintColor="transparent"
          onValueChange={val => setCurrentTime(val)}
          onSlidingComplete={val => {
            player.currentTime = val
            if (isPlaying) {
              player.play()
            }
          }}
        />
        <Pressable className="flex-1 items-start" onPress={toggleMute}>
          <FontAwesomeIcon name={isMuted ? 'volume-xmark' : 'volume-low'} size={24} color="#fff" />
        </Pressable>
      </View>
    </View>
  </View>
)
guest

0 評論
最舊
最新 最多投票
內聯回饋
查看全部評論