videoJsPlayer.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import { RefObject, useEffect, useRef, useState } from 'react'
  2. import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js'
  3. import 'video.js/dist/video-js.css'
  4. import { VideoFields_media_location } from '@/api/queries/__generated__/VideoFields'
  5. import { STORAGE_NODE_URL } from '@/config/urls'
  6. export type VideoJsConfig = {
  7. src: VideoFields_media_location
  8. width?: number
  9. height?: number
  10. fluid?: boolean
  11. fill?: boolean
  12. muted?: boolean
  13. posterUrl?: string
  14. onDataLoaded?: () => void
  15. onPlay?: () => void
  16. onPause?: () => void
  17. }
  18. const createJoystreamStorageUrl = (dataObjectId: string) => {
  19. const url = new URL(dataObjectId, STORAGE_NODE_URL)
  20. return url.href
  21. }
  22. type VideoJsPlayerHook = (config: VideoJsConfig) => [VideoJsPlayer | null, RefObject<HTMLVideoElement>]
  23. export const useVideoJsPlayer: VideoJsPlayerHook = ({
  24. fill,
  25. fluid,
  26. height,
  27. src,
  28. width,
  29. muted = false,
  30. posterUrl,
  31. onDataLoaded,
  32. onPlay,
  33. onPause,
  34. }) => {
  35. const playerRef = useRef<HTMLVideoElement>(null)
  36. const [player, setPlayer] = useState<VideoJsPlayer | null>(null)
  37. const parsedSource = src.__typename === 'HttpMediaLocation' ? src.url : createJoystreamStorageUrl(src.dataObjectId)
  38. useEffect(() => {
  39. const videoJsOptions: VideoJsPlayerOptions = {
  40. controls: true,
  41. // @ts-ignore @types/video.js is outdated and doesn't provide types for some newer video.js features
  42. playsinline: true,
  43. }
  44. const playerInstance = videojs(playerRef.current, videoJsOptions)
  45. setPlayer(playerInstance)
  46. return () => {
  47. playerInstance.dispose()
  48. }
  49. }, [])
  50. useEffect(() => {
  51. if (!player) {
  52. return
  53. }
  54. player.src({
  55. src: parsedSource,
  56. type: 'video/mp4',
  57. })
  58. }, [player, parsedSource])
  59. useEffect(() => {
  60. if (!player || !width) {
  61. return
  62. }
  63. player.width(width)
  64. }, [player, width])
  65. useEffect(() => {
  66. if (!player || !height) {
  67. return
  68. }
  69. player.height(height)
  70. }, [player, height])
  71. useEffect(() => {
  72. if (!player) {
  73. return
  74. }
  75. player.fluid(Boolean(fluid))
  76. }, [player, fluid])
  77. useEffect(() => {
  78. if (!player) {
  79. return
  80. }
  81. // @ts-ignore @types/video.js is outdated and doesn't provide types for some newer video.js features
  82. player.fill(Boolean(fill))
  83. }, [player, fill])
  84. useEffect(() => {
  85. if (!player) {
  86. return
  87. }
  88. player.muted(muted)
  89. }, [player, muted])
  90. useEffect(() => {
  91. if (!player || !posterUrl) {
  92. return
  93. }
  94. player.poster(posterUrl)
  95. }, [player, posterUrl])
  96. useEffect(() => {
  97. if (!player || !onDataLoaded) {
  98. return
  99. }
  100. player.on('loadeddata', onDataLoaded)
  101. return () => {
  102. player.off('loadeddata', onDataLoaded)
  103. }
  104. }, [player, onDataLoaded])
  105. useEffect(() => {
  106. if (!player || !onPlay) {
  107. return
  108. }
  109. player.on('play', onPlay)
  110. return () => {
  111. player.off('play', onPlay)
  112. }
  113. }, [player, onPlay])
  114. useEffect(() => {
  115. if (!player || !onPause) {
  116. return
  117. }
  118. player.on('pause', onPause)
  119. return () => {
  120. player.off('pause', onPause)
  121. }
  122. }, [player, onPause])
  123. return [player, playerRef]
  124. }