@banou/media-player 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,5 +6,6 @@ declare const _default: ({ ref, defaultValue }: {
6
6
  value: number | undefined;
7
7
  scrubbing: boolean;
8
8
  scrub: MouseEventHandler<HTMLDivElement>;
9
+ setValue: import("react").Dispatch<import("react").SetStateAction<number | undefined>>;
9
10
  };
10
11
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@banou/media-player",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -43,6 +43,6 @@
43
43
  "p-queue": "^7.3.4",
44
44
  "react": "^18.2.0",
45
45
  "react-feather": "^2.0.10",
46
- "react-tooltip": "^4.2.21"
46
+ "react-tooltip": "^5.26.3"
47
47
  }
48
48
  }
@@ -1,10 +1,9 @@
1
1
  /// <reference types="@emotion/react/types/css-prop" />
2
- import type { Dispatch, DOMAttributes, HTMLAttributes, MouseEvent, MouseEventHandler, SetStateAction } from 'react'
2
+ import type { Dispatch, DOMAttributes, HTMLAttributes, MouseEvent, MouseEventHandler, MutableRefObject, SetStateAction } from 'react'
3
3
 
4
4
  import type { ChromeOptions } from '.'
5
5
  import type { FKNVideoControl, Subtitle, TransmuxError } from '..'
6
6
 
7
- import ReactTooltip from 'react-tooltip'
8
7
  import { css } from '@emotion/react'
9
8
  import { useEffect, useMemo, useRef, useState } from 'react'
10
9
  import { Volume, Volume1, Volume2, VolumeX, AlertTriangle } from 'react-feather'
@@ -12,6 +11,7 @@ import { Volume, Volume1, Volume2, VolumeX, AlertTriangle } from 'react-feather'
12
11
  import useScrub from '../use-scrub'
13
12
  import { tagToLanguage } from '../languages'
14
13
  import useNamespacedLocalStorage from '../use-local-storage'
14
+ import { Tooltip } from 'react-tooltip'
15
15
 
16
16
  const style = css`
17
17
  position: relative;
@@ -275,6 +275,8 @@ const style = css`
275
275
  `
276
276
 
277
277
  export type BottomOptions = {
278
+ video: MutableRefObject<HTMLVideoElement | undefined>
279
+ videoReactive: HTMLVideoElement | undefined,
278
280
  duration?: number
279
281
  currentTime?: number
280
282
  togglePlay: () => void
@@ -293,6 +295,8 @@ Pick<ChromeOptions, 'seek' | 'setVolume' | 'loadedTime' | 'isPlaying' | 'tracks'
293
295
  HTMLAttributes<HTMLDivElement>
294
296
 
295
297
  export default ({
298
+ video,
299
+ videoReactive,
296
300
  duration,
297
301
  currentTime,
298
302
  togglePlay,
@@ -317,14 +321,14 @@ export default ({
317
321
  const progressBarRef = useRef<HTMLDivElement>(null)
318
322
  const volumeBarRef = useRef<HTMLDivElement>(null)
319
323
  const [hiddenVolumeArea, setHiddenVolumeArea] = useState(false)
320
- const { scrub: seekScrub, value: seekScrubValue } = useScrub({ ref: progressBarRef })
324
+ const { scrub: seekScrub, value: seekScrubValue, setValue: setSeekValue } = useScrub({ ref: progressBarRef })
321
325
  const isPictureInPictureEnabled = useMemo(() => document.pictureInPictureEnabled, [])
322
326
  const [hasShownErrorPopup, setHasShownErrorPopup] = useState(false)
323
327
 
324
328
  const useStoredValue = useNamespacedLocalStorage<{ volume: number, muted: boolean }>('fkn-media-player')
325
329
  const [volume, setStoredVolume] = useStoredValue('volume', 1)
326
330
  const [isMuted, setStoredMuted] = useStoredValue('muted', false)
327
- const { scrub: volumeScrub, value: volumeScrubValue } = useScrub({ ref: volumeBarRef, defaultValue: volume })
331
+ const { scrub: volumeScrub, value: volumeScrubValue, setValue: updateVolume } = useScrub({ ref: volumeBarRef, defaultValue: volume })
328
332
 
329
333
  useEffect(() => {
330
334
  if (hasShownErrorPopup) return
@@ -442,19 +446,46 @@ export default ({
442
446
  .map(str => <div key={str} className="error-menu-message">{str}</div>)
443
447
  , [errors])
444
448
 
445
- useEffect(() => {
446
- ReactTooltip.rebuild()
447
- }, [tracks.length])
448
-
449
449
  useEffect(() => {
450
450
  const eventListener = (ev: KeyboardEvent) => {
451
451
  if (ev.key === 'f') toggleFullscreen()
452
452
  if (ev.key === 'k') togglePlay()
453
+ if (ev.key === ' ') togglePlay()
453
454
  if (ev.key === 'm') toggleMuteButton()
455
+ if (ev.key === 'ArrowUp') {
456
+ if (volume <= 0.95) {
457
+ updateVolume(volume + 0.05)
458
+ } else {
459
+ updateVolume(1)
460
+ }
461
+ }
462
+ if (ev.key === 'ArrowDown') {
463
+ if (volume >= 0.05) {
464
+ updateVolume(volume - 0.05)
465
+ } else {
466
+ updateVolume(0)
467
+ }
468
+ }
469
+ if (ev.key === 'ArrowLeft') {
470
+ if (!videoReactive) return
471
+ videoReactive.currentTime -= (
472
+ ev.shiftKey ? 20
473
+ : ev.ctrlKey ? 2
474
+ : 5
475
+ )
476
+ }
477
+ if (ev.key === 'ArrowRight') {
478
+ if (!videoReactive) return
479
+ videoReactive.currentTime += (
480
+ ev.shiftKey ? 20
481
+ : ev.ctrlKey ? 2
482
+ : 5
483
+ )
484
+ }
454
485
  }
455
486
  window.addEventListener('keydown', eventListener)
456
487
  return () => window.removeEventListener('keydown', eventListener)
457
- }, [toggleFullscreen, togglePlay, toggleMuteButton])
488
+ }, [videoReactive, toggleFullscreen, togglePlay, toggleMuteButton])
458
489
 
459
490
  const [progressBarOverTime, setProgressBarOverTime] = useState<number | undefined>(undefined)
460
491
 
@@ -519,14 +550,14 @@ export default ({
519
550
  <div className="padding" onMouseDown={seekScrub}></div>
520
551
  </div>
521
552
  <div className="controls">
522
- <ReactTooltip id="play-button-tooltip" effect="solid" place="top">
553
+ <Tooltip id="play-button-tooltip" place="top-start" >
523
554
  {
524
555
  isPlaying
525
556
  ? 'Pause (k)'
526
557
  : 'Play (k)'
527
558
  }
528
- </ReactTooltip>
529
- <button className="play-button" type="button" data-tip data-for="play-button-tooltip" onClick={togglePlay}>
559
+ </Tooltip>
560
+ <button className="play-button" type="button" data-tip data-tooltip-id="play-button-tooltip" onClick={togglePlay}>
530
561
  {
531
562
  isPlaying
532
563
  ? <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-pause"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>
@@ -534,14 +565,17 @@ export default ({
534
565
  }
535
566
  </button>
536
567
  <div className="volume-area" onMouseOver={hoverVolumeArea}>
537
- <ReactTooltip id="mute-button-tooltip" effect="solid" place="top">
568
+ <Tooltip
569
+ id="mute-button-tooltip"
570
+ place="top"
571
+ >
538
572
  {
539
573
  isMuted
540
574
  ? 'Unmute (m)'
541
575
  : 'Mute (m)'
542
576
  }
543
- </ReactTooltip>
544
- <button className="mute-button" data-tip data-for="mute-button-tooltip" onClick={toggleMuteButton}>
577
+ </Tooltip>
578
+ <button className="mute-button" data-tip data-tooltip-id="mute-button-tooltip" onClick={toggleMuteButton}>
545
579
  {
546
580
  isMuted ? <VolumeX/>
547
581
  : (volume ?? 0) > 0.66 ? <Volume2/>
@@ -589,9 +623,17 @@ export default ({
589
623
  : null
590
624
  }
591
625
  </div>
592
- <ReactTooltip id="subtitle-button-tooltip" globalEventOff="click" effect="solid" place="top" disable={!isSubtitleMenuHidden}>
593
- Subtitles
594
- </ReactTooltip>
626
+ <Tooltip
627
+ id="subtitle-button-tooltip"
628
+ globalEventOff="click"
629
+
630
+ disable={!isSubtitleMenuHidden}
631
+ class="tooltip"
632
+ >
633
+ <span>
634
+ Subtitles
635
+ </span>
636
+ </Tooltip>
595
637
  <div className="subtitle-area">
596
638
  {
597
639
  tracks.length
@@ -610,7 +652,7 @@ export default ({
610
652
  )
611
653
  }
612
654
  </div>
613
- <button className="subtitle-menu-button" data-tip data-for="subtitle-button-tooltip" onClick={subtitleMenuButtonClick}>
655
+ <button className="subtitle-menu-button" data-tip data-tooltip-id="subtitle-button-tooltip" onClick={subtitleMenuButtonClick}>
614
656
  <SubtitleTrackToName subtitleTrack={subtitleTrack}/>
615
657
  </button>
616
658
  </>
@@ -618,26 +660,30 @@ export default ({
618
660
  : null
619
661
  }
620
662
  </div>
621
- <ReactTooltip id="pip-button-tooltip" effect="solid" place="top">
663
+ <Tooltip
664
+ id="pip-button-tooltip"
665
+
666
+ place="top"
667
+ >
622
668
  Picture in Picture
623
- </ReactTooltip>
669
+ </Tooltip>
624
670
  {
625
671
  isPictureInPictureEnabled
626
672
  ? (
627
- <button className="picture-in-picture" data-tip data-for="pip-button-tooltip" onClick={pictureInPicture}>
673
+ <button className="picture-in-picture" data-tip data-tooltip-id="pip-button-tooltip" onClick={pictureInPicture}>
628
674
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="3" width="21" height="14" rx="2" ry="2"></rect><rect x="12.5" y="8.5" width="8" height="6" rx="2" ry="2"></rect></svg>
629
675
  </button>
630
676
  )
631
677
  : null
632
678
  }
633
- <ReactTooltip id="fullscreen-button-tooltip" effect="solid" place="top">
679
+ <Tooltip id="fullscreen-button-tooltip" place="top-end">
634
680
  {
635
681
  isFullscreen
636
682
  ? 'Exit full screen (f)'
637
683
  : 'Full screen (f)'
638
684
  }
639
- </ReactTooltip>
640
- <button className="fullscreen" data-tip data-for="fullscreen-button-tooltip" onClick={toggleFullscreen}>
685
+ </Tooltip>
686
+ <button className="fullscreen" data-tip data-tooltip-id="fullscreen-button-tooltip" onClick={toggleFullscreen}>
641
687
  {
642
688
  isFullscreen
643
689
  ? <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-minimize"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"></path></svg>
@@ -30,6 +30,7 @@ overflow: hidden;
30
30
  `
31
31
 
32
32
  export type ChromeOptions = {
33
+ publicPath: string
33
34
  customOverlay?: ReactNode
34
35
  isPlaying?: boolean
35
36
  loading?: boolean
@@ -45,6 +46,7 @@ export type ChromeOptions = {
45
46
  attachments: Attachment[] | undefined
46
47
  tracks: Subtitle[]
47
48
  video: MutableRefObject<HTMLVideoElement | undefined>
49
+ videoReactive: HTMLVideoElement | undefined,
48
50
  errors: TransmuxError[]
49
51
  customControls?: FKNVideoControl[]
50
52
  libassWorkerUrl: string
@@ -54,6 +56,7 @@ export type ChromeOptions = {
54
56
 
55
57
  export default ({
56
58
  customOverlay,
59
+ publicPath,
57
60
  isPlaying,
58
61
  loading,
59
62
  duration,
@@ -68,6 +71,7 @@ export default ({
68
71
  attachments,
69
72
  tracks,
70
73
  video,
74
+ videoReactive,
71
75
  errors,
72
76
  customControls,
73
77
  libassWorkerUrl,
@@ -137,7 +141,7 @@ export default ({
137
141
  canvas: canvasElement,
138
142
  subContent: subtitleTrack.data,
139
143
  fonts: fonts.filter(Boolean).map(([,filename]) => filename as string),
140
- availableFonts: { ...Object.fromEntries(fonts), 'liberation sans': new URL('/build/default.woff2', new URL(window.location.toString()).origin).toString() },
144
+ availableFonts: { ...Object.fromEntries(fonts), 'liberation sans': `${publicPath}default.woff2` },
141
145
  workerUrl: libassWorkerUrl, // Link to WebAssembly-based file "libassjs-worker.js",
142
146
  modernWasmUrl: wasmUrl
143
147
  })
@@ -176,6 +180,8 @@ export default ({
176
180
  togglePlay={togglePlay}
177
181
  isFullscreen={isFullscreen}
178
182
  pictureInPicture={pictureInPicture}
183
+ video={video}
184
+ videoReactive={videoReactive}
179
185
  seek={seek}
180
186
  setCurrentSubtitleTrack={setCurrentSubtitleTrack}
181
187
  isSubtitleMenuHidden={isSubtitleMenuHidden}
package/src/index.tsx CHANGED
@@ -447,8 +447,10 @@ const FKNVideo = forwardRef<HTMLVideoElement, VideoHTMLAttributes<HTMLInputEleme
447
447
  <Chrome
448
448
  className="chrome"
449
449
  customOverlay={customOverlay}
450
+ publicPath={publicPath}
450
451
  isPlaying={isPlaying}
451
452
  video={videoRef}
453
+ videoReactive={videoElement}
452
454
  needsInitialInteraction={needsInitialInteraction}
453
455
  loading={loading}
454
456
  duration={duration}
package/src/main.tsx CHANGED
@@ -25,7 +25,7 @@ const Mount = () => {
25
25
 
26
26
  const onFetch = async (offset: number, end?: number) =>
27
27
  fetch(
28
- '/video9.mkv',
28
+ '/video.mkv',
29
29
  {
30
30
  headers: {
31
31
  Range: `bytes=${offset}-${end ?? (!BACKPRESSURE_STREAM_ENABLED ? Math.min(offset + BASE_BUFFER_SIZE, size!) : '')}`
package/src/use-scrub.ts CHANGED
@@ -34,6 +34,7 @@ export default ({ ref, defaultValue }: { ref: RefObject<HTMLElement>, defaultVal
34
34
  return {
35
35
  value,
36
36
  scrubbing,
37
- scrub
37
+ scrub,
38
+ setValue
38
39
  }
39
40
  }
package/src/vite-env.d.ts CHANGED
@@ -1 +1 @@
1
- /// <reference types="vite/client" />
1
+ /// <reference types="vite/client" />
@@ -1,9 +1,9 @@
1
- {
2
- "compilerOptions": {
3
- "composite": true,
4
- "module": "ESNext",
5
- "moduleResolution": "Node",
6
- "allowSyntheticDefaultImports": true
7
- },
8
- "include": ["vite.config.ts"]
9
- }
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "allowSyntheticDefaultImports": true
7
+ },
8
+ "include": ["vite.config.ts"]
9
+ }