下方的播放/暫停按鈕、進度條、音量按鈕都是自定義的。
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 需使用 onValueChange及onSlidingComplete去更新當前播放時間- 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>
)

![[React] Redux Toolkit Query(RTKQ) 基礎使用指南 2 RTKQ logo](https://www.may-notes.com/wp-content/uploads/2023/12/nl9bkr5l1h5ke31vkula-150x150.webp)