use-captions

PreviousNext
Docs
limeplayhook

Preview

Loading preview…
registry/default/hooks/use-captions.ts
"use client"

import type shaka from "shaka-player"
import type { StateCreator } from "zustand"

import { useCallback, useEffect } from "react"

import type { PlayerStore } from "@/registry/default/hooks/use-player"

import { getDeviceLanguage, off, on } from "@/registry/default/lib/utils"
import {
  useGetStore,
  useMediaStore,
} from "@/registry/default/ui/media-provider"

export interface CaptionsStore {
  activeTextTrack: null | shaka.extern.TextTrack
  setTextTrackContainerElement: (ref: HTMLDivElement | null) => void
  textTrackContainerElement: HTMLDivElement | null
  textTracks?: shaka.extern.TextTrack[]
  textTrackVisible: boolean
}

export const createCaptionsStore: StateCreator<
  CaptionsStore & PlayerStore,
  [],
  [],
  CaptionsStore
> = (set) => ({
  activeTextTrack: null,
  setTextTrackContainerElement: (element: HTMLDivElement | null) => {
    set({
      textTrackContainerElement: element,
    })
  },
  textTrackContainerElement: null,
  textTracks: undefined,
  textTrackVisible: false,
})

export function useCaptions() {
  const store = useGetStore()
  const player = useMediaStore((s) => s.player)
  const activeTextTrack = useMediaStore((s) => s.activeTextTrack)
  const textTracks = useMediaStore((s) => s.textTracks)

  const findDefaultTrack = useCallback(() => {
    if (!textTracks) {
      console.warn("No text tracks found")
      return
    }

    if (textTracks.length === 1) {
      return textTracks[0]
    }

    const deviceLanguage = getDeviceLanguage()

    const regionalTrack = textTracks.find(
      (track) => track.language === deviceLanguage
    )

    if (regionalTrack) {
      return regionalTrack
    }

    return textTracks[0]
  }, [textTracks])

  const selectTrack = useCallback(
    (track: shaka.extern.TextTrack) => {
      if (!player || !textTracks) {
        return false
      }

      player.selectTextTrack(track)

      const activeTextTrack = player
        .getTextTracks()
        .find((t: shaka.extern.TextTrack) => t.active)

      store.setState({ activeTextTrack })

      return true
    },
    [player, textTracks]
  )

  const toggleCaptionVisibility = () => {
    if (!player) {
      return
    }

    if (!activeTextTrack) {
      const defaultTrack = findDefaultTrack()
      if (defaultTrack) {
        const isSuccess = selectTrack(defaultTrack)

        if (!isSuccess) {
          console.error("Failed to select default text track")
          return
        }
      }
    }

    const isVisible = store.getState().textTrackVisible
    player.setTextTrackVisibility(!isVisible)
  }

  return {
    toggleCaptionVisibility,
  }
}

export function useCaptionsStates() {
  const store = useGetStore()
  const player = useMediaStore((s) => s.player)
  const containerElement = useMediaStore((s) => s.textTrackContainerElement)
  const mediaRef = useMediaStore((state) => state.mediaRef)
  const canPlay = useMediaStore((state) => state.canPlay)

  const onTextTrackChanged = () => {
    if (!player) {
      return
    }

    const activeTextTrack = player
      .getTextTracks()
      .find((t: shaka.extern.TextTrack) => t.active)

    store.setState({ activeTextTrack })
  }

  const onTracksChanged = () => {
    if (!player) {
      return
    }

    const tracks = player.getTextTracks()
    store.setState({ textTracks: tracks })
  }

  const onTextTrackVisibility = () => {
    if (!player) {
      return
    }

    const isVisible = player.isTextTrackVisible()

    store.setState({ textTrackVisible: isVisible })
  }

  useEffect(() => {
    if (!player || !containerElement) {
      return
    }

    player.setVideoContainer(containerElement)
  }, [containerElement, player])

  useEffect(() => {
    if (!mediaRef.current || !player) return

    if (canPlay) {
      onTracksChanged()
    }

    on(player, "textchanged", onTextTrackChanged)
    on(player, ["trackschanged", "loading"], onTracksChanged)
    on(player, "texttrackvisibility", onTextTrackVisibility)

    return () => {
      off(player, "textchanged", onTextTrackChanged)
      off(player, ["trackschanged", "loading"], onTracksChanged)
      off(player, "texttrackvisibility", onTextTrackVisibility)
    }
  }, [mediaRef, player, canPlay])
}

Installation

npx shadcn@latest add @limeplay/use-captions

Usage

import { UseCaptions } from "@/hooks/use-captions"
const value = UseCaptions()