videojs-vue 0.0.0 → 0.0.1

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.
Files changed (114) hide show
  1. package/README.md +183 -4
  2. package/dist/AlertDialog-BPntemoC.js +112 -0
  3. package/dist/Audio-3HgRIaNt.js +29 -0
  4. package/dist/Audio-rV5KrK6q.d.ts +7 -0
  5. package/dist/BufferingIndicator-Crzitkyo.js +64 -0
  6. package/dist/CaptionsButton-DZvnZsRf.js +124 -0
  7. package/dist/CastButton-B645yeE0.js +32 -0
  8. package/dist/Controls-CEaWmHRW.js +33 -0
  9. package/dist/ErrorDialog-Csv288q6.js +60 -0
  10. package/dist/FullscreenButton-C29YwATz.js +59 -0
  11. package/dist/Gesture-DFE17PKp.js +75 -0
  12. package/dist/Hotkey-DbQF7Kcc.js +72 -0
  13. package/dist/MuteButton-DI9PgX0j.js +47 -0
  14. package/dist/PiPButton-Da-rW3zX.js +55 -0
  15. package/dist/PlayButton-D8BRNiiy.js +51 -0
  16. package/dist/PlaybackRateButton-BL6cly9Z.js +60 -0
  17. package/dist/Popover-BoMi7OiJ.js +94 -0
  18. package/dist/Poster-uAFVQvx8.js +33 -0
  19. package/dist/SeekButton-VPcC9jiz.js +48 -0
  20. package/dist/Slider-C8FCipIJ.js +127 -0
  21. package/dist/Thumbnail-D1iL13r4.js +48 -0
  22. package/dist/Time-pfGZovLG.js +77 -0
  23. package/dist/TimeSlider-dlcFBLwL.js +147 -0
  24. package/dist/Tooltip-DyEkP_xv.js +119 -0
  25. package/dist/Video-DZX6iEK6.d.ts +7 -0
  26. package/dist/Video-DtjXh6lt.js +29 -0
  27. package/dist/VolumeSlider-C_nTFP4G.js +148 -0
  28. package/dist/alert-dialog.d.ts +2 -0
  29. package/dist/alert-dialog.js +2 -0
  30. package/dist/audio.d.ts +3 -0
  31. package/dist/audio.js +3 -0
  32. package/dist/background.d.ts +8 -0
  33. package/dist/background.js +19 -0
  34. package/dist/buffering-indicator.d.ts +2 -0
  35. package/dist/buffering-indicator.js +2 -0
  36. package/dist/captions-button.d.ts +2 -0
  37. package/dist/captions-button.js +2 -0
  38. package/dist/cast-button.d.ts +2 -0
  39. package/dist/cast-button.js +2 -0
  40. package/dist/context-C48Uabdu.js +83 -0
  41. package/dist/controls.d.ts +2 -0
  42. package/dist/controls.js +2 -0
  43. package/dist/createParts-Cq1bDfQi.d.ts +20 -0
  44. package/dist/createParts-DQhQPfRO.js +115 -0
  45. package/dist/error-dialog.d.ts +2 -0
  46. package/dist/error-dialog.js +2 -0
  47. package/dist/formatClock-D8z33znG.js +12 -0
  48. package/dist/full-screen-button.d.ts +2 -0
  49. package/dist/full-screen-button.js +2 -0
  50. package/dist/gesture.d.ts +2 -0
  51. package/dist/gesture.js +2 -0
  52. package/dist/hotkey.d.ts +2 -0
  53. package/dist/hotkey.js +2 -0
  54. package/dist/index-B3CYxKTC.d.ts +12 -0
  55. package/dist/index-B6WjYRnz.d.ts +33 -0
  56. package/dist/index-BJwWXroQ.d.ts +33 -0
  57. package/dist/index-BQN2NnER.d.ts +127 -0
  58. package/dist/index-BfzCw_wH.d.ts +71 -0
  59. package/dist/index-Bt_p2vCi.d.ts +176 -0
  60. package/dist/index-Bx7oSSQd.d.ts +33 -0
  61. package/dist/index-C8DkRbnH.d.ts +108 -0
  62. package/dist/index-CSs1G8z6.d.ts +7 -0
  63. package/dist/index-CcKZD20a.d.ts +90 -0
  64. package/dist/index-Cp23VNuH.d.ts +23 -0
  65. package/dist/index-Cry-CeV9.d.ts +57 -0
  66. package/dist/index-D5lfnRsK.d.ts +20 -0
  67. package/dist/index-D8EBYIXG.d.ts +38 -0
  68. package/dist/index-DYxPIvnt.d.ts +119 -0
  69. package/dist/index-DrelWSL1.d.ts +50 -0
  70. package/dist/index-EsA6hZWc.d.ts +23 -0
  71. package/dist/index-RI8VZ21i.d.ts +69 -0
  72. package/dist/index-iTm4WaZf.d.ts +33 -0
  73. package/dist/index-lmsQv-dG.d.ts +33 -0
  74. package/dist/index-w4PLuacs.d.ts +33 -0
  75. package/dist/index-ygLHhQAa.d.ts +7 -0
  76. package/dist/index.d.ts +129 -3
  77. package/dist/index.js +102 -3
  78. package/dist/live-audio.d.ts +6 -0
  79. package/dist/live-audio.js +6 -0
  80. package/dist/live-video.d.ts +6 -0
  81. package/dist/live-video.js +6 -0
  82. package/dist/mediaActions-Bhs90znv.js +39 -0
  83. package/dist/mute-button.d.ts +2 -0
  84. package/dist/mute-button.js +2 -0
  85. package/dist/pip-button.d.ts +2 -0
  86. package/dist/pip-button.js +2 -0
  87. package/dist/play-button.d.ts +2 -0
  88. package/dist/play-button.js +2 -0
  89. package/dist/playback-rate-button.d.ts +2 -0
  90. package/dist/playback-rate-button.js +2 -0
  91. package/dist/popover.d.ts +2 -0
  92. package/dist/popover.js +2 -0
  93. package/dist/poster.d.ts +2 -0
  94. package/dist/poster.js +2 -0
  95. package/dist/renderElement-D_UbvLk5.js +114 -0
  96. package/dist/seek-button.d.ts +2 -0
  97. package/dist/seek-button.js +2 -0
  98. package/dist/slider.d.ts +3 -0
  99. package/dist/slider.js +2 -0
  100. package/dist/thumbnail.d.ts +2 -0
  101. package/dist/thumbnail.js +2 -0
  102. package/dist/time-slider.d.ts +3 -0
  103. package/dist/time-slider.js +2 -0
  104. package/dist/time.d.ts +2 -0
  105. package/dist/time.js +2 -0
  106. package/dist/tooltip.d.ts +2 -0
  107. package/dist/tooltip.js +2 -0
  108. package/dist/useMediaEvents-Br7vzJ_l.js +17 -0
  109. package/dist/useSyncMediaProps-C7F52G6b.js +95 -0
  110. package/dist/video.d.ts +3 -0
  111. package/dist/video.js +3 -0
  112. package/dist/volume-slider.d.ts +3 -0
  113. package/dist/volume-slider.js +2 -0
  114. package/package.json +132 -16
package/README.md CHANGED
@@ -5,13 +5,17 @@
5
5
  [![NPM DOWNLOADS](https://img.shields.io/npm/dy/videojs-vue.svg)](https://www.npmjs.com/package/videojs-vue)
6
6
  [![LICENSE](https://img.shields.io/github/license/ntnyq/videojs-vue.svg)](https://github.com/ntnyq/videojs-vue/blob/main/LICENSE)
7
7
 
8
- A VueJS plugin for Videojs v10.
8
+ A Vue-first wrapper around the Video.js v10 player primitives.
9
9
 
10
10
  > [!NOTE]
11
- > Status: **Work in progress**
11
+ > Status: **Beta**
12
12
 
13
13
  ## Install
14
14
 
15
+ ```shell
16
+ pnpm add videojs-vue
17
+ ```
18
+
15
19
  ```shell
16
20
  npm install videojs-vue
17
21
  ```
@@ -20,10 +24,185 @@ npm install videojs-vue
20
24
  yarn add videojs-vue
21
25
  ```
22
26
 
23
- ```shell
24
- pnpm add videojs-vue
27
+ ## Usage
28
+
29
+ The API follows the upstream `@videojs/react` player factory:
30
+
31
+ ```ts
32
+ import { createPlayer } from 'videojs-vue'
33
+ import { Video, videoFeatures } from 'videojs-vue/video'
34
+
35
+ const Player = createPlayer({
36
+ features: videoFeatures,
37
+ displayName: 'VideoPlayer',
38
+ })
25
39
  ```
26
40
 
41
+ Render it with Vue's `h()` API or JSX:
42
+
43
+ ```ts
44
+ import { defineComponent, h } from 'vue'
45
+
46
+ export default defineComponent({
47
+ setup() {
48
+ const paused = Player.usePlayer(state => state.paused)
49
+ const store = Player.usePlayer()
50
+
51
+ return () =>
52
+ h(Player.Provider, () =>
53
+ h(Player.Container, { class: 'player-shell' }, () => [
54
+ h(Video, {
55
+ src: 'https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4',
56
+ playsInline: true,
57
+ }),
58
+ h(
59
+ 'button',
60
+ {
61
+ type: 'button',
62
+ onClick: () => (paused.value ? store.play() : store.pause()),
63
+ },
64
+ paused.value ? 'Play' : 'Pause',
65
+ ),
66
+ ]),
67
+ )
68
+ },
69
+ })
70
+ ```
71
+
72
+ ## API
73
+
74
+ ### Root exports
75
+
76
+ - `createPlayer(config)` returns:
77
+ - `Provider`
78
+ - `Container`
79
+ - `usePlayer()`
80
+ - `useMedia()`
81
+ - `createPlayer({ plugins })` supports runtime extension hooks:
82
+ - `onProviderSetup(context)`
83
+ - `onAttach(context)`
84
+ - `onDetach(context)`
85
+ - `onProviderDestroy(context)`
86
+ - `usePlayer(selector)` returns a readonly `ref`, which is the Vue equivalent of the React hook's reactive selected value.
87
+ - `useOptionalPlayer()`, `useContainer()`, `useMediaAttach()` and `useContainerAttach()` are also exported for lower-level integrations.
88
+ - Utility exports:
89
+ - `mergeProps(...props)`
90
+ - `renderElement(tag, componentProps, params)`
91
+
92
+ ### Preset entry points
93
+
94
+ - `videojs-vue/video`
95
+ - `Video`
96
+ - `videoFeatures`
97
+ - `videojs-vue/audio`
98
+ - `Audio`
99
+ - `audioFeatures`
100
+ - `videojs-vue/background`
101
+ - `BackgroundVideo`
102
+ - `backgroundFeatures`
103
+ - `videojs-vue/live-video`
104
+ - `Video`
105
+ - `liveVideoFeatures`
106
+ - `videojs-vue/live-audio`
107
+ - `Audio`
108
+ - `liveAudioFeatures`
109
+
110
+ ### UI entry points
111
+
112
+ - `videojs-vue/full-screen-button` (or `videojs-vue/fullscreen-button`)
113
+ - `FullscreenButton`
114
+ - `videojs-vue/playback-rate-button`
115
+ - `PlaybackRateButton`
116
+ - `videojs-vue/poster`
117
+ - `Poster`
118
+ - `videojs-vue/time`
119
+ - `Time.Group`
120
+ - `Time.Separator`
121
+ - `Time.Value`
122
+ - `videojs-vue/play-button`
123
+ - `PlayButton`
124
+ - `videojs-vue/mute-button`
125
+ - `MuteButton`
126
+ - `videojs-vue/pip-button`
127
+ - `PiPButton`
128
+ - `videojs-vue/seek-button`
129
+ - `SeekButton`
130
+ - `videojs-vue/volume-slider`
131
+ - `VolumeSlider.Root`
132
+ - `VolumeSlider.Track`
133
+ - `VolumeSlider.Fill`
134
+ - `VolumeSlider.Thumb`
135
+ - `VolumeSlider.Value`
136
+ - `VolumeSlider.Preview`
137
+ - `videojs-vue/time-slider`
138
+ - `TimeSlider.Root`
139
+ - `TimeSlider.Track`
140
+ - `TimeSlider.Buffer`
141
+ - `TimeSlider.Fill`
142
+ - `TimeSlider.Thumb`
143
+ - `TimeSlider.Value`
144
+ - `TimeSlider.Preview`
145
+ - `videojs-vue/controls`
146
+ - `Controls.Root`
147
+ - `Controls.Group`
148
+ - `videojs-vue/tooltip`
149
+ - `Tooltip.Provider`
150
+ - `Tooltip.Root`
151
+ - `Tooltip.Trigger`
152
+ - `Tooltip.Popup`
153
+ - `Tooltip.Arrow`
154
+ - `videojs-vue/popover`
155
+ - `Popover.Root`
156
+ - `Popover.Trigger`
157
+ - `Popover.Popup`
158
+ - `Popover.Arrow`
159
+ - `videojs-vue/captions-button`
160
+ - `CaptionsButton`
161
+ - `videojs-vue/cast-button`
162
+ - `CastButton`
163
+ - `videojs-vue/buffering-indicator`
164
+ - `BufferingIndicator`
165
+ - `videojs-vue/alert-dialog`
166
+ - `AlertDialog.Root`
167
+ - `AlertDialog.Popup`
168
+ - `AlertDialog.Title`
169
+ - `AlertDialog.Description`
170
+ - `AlertDialog.Close`
171
+ - `videojs-vue/error-dialog`
172
+ - `ErrorDialog.Root`
173
+ - `ErrorDialog.Popup`
174
+ - `ErrorDialog.Title`
175
+ - `ErrorDialog.Description`
176
+ - `ErrorDialog.Close`
177
+ - `videojs-vue/hotkey`
178
+ - `Hotkey`
179
+ - `videojs-vue/gesture`
180
+ - `Gesture`
181
+ - `videojs-vue/thumbnail`
182
+ - `Thumbnail`
183
+ - `videojs-vue/slider`
184
+ - `Slider.Root`
185
+ - `Slider.Track`
186
+ - `Slider.Buffer`
187
+ - `Slider.Fill`
188
+ - `Slider.Thumb`
189
+ - `Slider.Value`
190
+ - `Slider.Preview`
191
+ - `Slider.Thumbnail`
192
+
193
+ ## Notes
194
+
195
+ - `Provider` owns the Video.js store instance and reattaches it whenever the media element or container changes.
196
+ - `Container` keeps the same focus behavior as the React implementation: on pointer release it focuses itself when nothing inside already has focus, so keyboard shortcuts can work reliably.
197
+ - Media components use native `<video>` / `<audio>` elements, register themselves automatically through provide/inject, and reactively sync key media props (`src`, `poster`, `autoplay`, `controls`, `loop`, `muted`, `playsInline`, `preload`, `crossOrigin`, `volume`, `playbackRate`).
198
+ - Captions button now uses reactive track state and auto-disables when no caption/subtitle tracks are available.
199
+
200
+ ## Migration (Current Beta)
201
+
202
+ - UI button primitives now use unified internal prop-merging behavior (`mergeProps`) instead of per-component manual click chaining.
203
+ - External event handlers still run before internal handlers and can cancel internal behavior via `event.preventDefault()`.
204
+ - Prefer `playsInline` over lowercase `playsinline` in JSX/templates.
205
+
27
206
  ## License
28
207
 
29
208
  [MIT](./LICENSE) License © 2026-PRESENT [ntnyq](https://github.com/ntnyq)
@@ -0,0 +1,112 @@
1
+ import { t as renderElement } from "./renderElement-D_UbvLk5.js";
2
+ import { computed, defineComponent, h, inject, provide, shallowRef, watch } from "vue";
3
+ //#region src/ui/alert-dialog/AlertDialog.ts
4
+ const ALERT_DIALOG_KEY = Symbol("videojs-alert-dialog");
5
+ function useAlertDialogContext() {
6
+ const context = inject(ALERT_DIALOG_KEY, null);
7
+ if (!context) throw new Error("AlertDialog parts must be used within AlertDialog.Root");
8
+ return context;
9
+ }
10
+ const AlertDialog = {
11
+ Root: defineComponent({
12
+ name: "VideoJsAlertDialogRoot",
13
+ inheritAttrs: false,
14
+ props: {
15
+ open: {
16
+ type: Boolean,
17
+ default: void 0
18
+ },
19
+ defaultOpen: {
20
+ type: Boolean,
21
+ default: false
22
+ },
23
+ onOpenChange: {
24
+ type: Function,
25
+ default: void 0
26
+ },
27
+ onOpenChangeComplete: {
28
+ type: Function,
29
+ default: void 0
30
+ }
31
+ },
32
+ setup(props, { attrs, slots }) {
33
+ const internalOpen = shallowRef(Boolean(props.defaultOpen));
34
+ const controlled = computed(() => props.open !== void 0);
35
+ const open = computed(() => controlled.value ? Boolean(props.open) : internalOpen.value);
36
+ function setOpen(nextOpen) {
37
+ if (!controlled.value) internalOpen.value = nextOpen;
38
+ props.onOpenChange?.(nextOpen);
39
+ props.onOpenChangeComplete?.(nextOpen);
40
+ }
41
+ watch(() => props.open, (value) => {
42
+ if (controlled.value && typeof value === "boolean") internalOpen.value = value;
43
+ }, { immediate: true });
44
+ provide(ALERT_DIALOG_KEY, {
45
+ open,
46
+ setOpen
47
+ });
48
+ return () => h("div", { ...attrs }, slots["default"]?.());
49
+ }
50
+ }),
51
+ Popup: defineComponent({
52
+ name: "VideoJsAlertDialogPopup",
53
+ inheritAttrs: false,
54
+ setup(_, { attrs, slots, expose }) {
55
+ const element = shallowRef(null);
56
+ const dialog = useAlertDialogContext();
57
+ expose({ el: element });
58
+ return () => {
59
+ if (!dialog.open.value) return null;
60
+ return h("div", {
61
+ ...attrs,
62
+ ref: ((value) => {
63
+ element.value = value;
64
+ }),
65
+ role: "alertdialog",
66
+ "aria-modal": "true"
67
+ }, slots["default"]?.());
68
+ };
69
+ }
70
+ }),
71
+ Title: defineComponent({
72
+ name: "VideoJsAlertDialogTitle",
73
+ inheritAttrs: false,
74
+ setup(_, { attrs, slots }) {
75
+ return () => h("h2", { ...attrs }, slots["default"]?.());
76
+ }
77
+ }),
78
+ Description: defineComponent({
79
+ name: "VideoJsAlertDialogDescription",
80
+ inheritAttrs: false,
81
+ setup(_, { attrs, slots }) {
82
+ return () => h("p", { ...attrs }, slots["default"]?.());
83
+ }
84
+ }),
85
+ Close: defineComponent({
86
+ name: "VideoJsAlertDialogClose",
87
+ inheritAttrs: false,
88
+ setup(_, { attrs, slots, expose }) {
89
+ const element = shallowRef(null);
90
+ const dialog = useAlertDialogContext();
91
+ expose({ el: element });
92
+ return () => {
93
+ const content = slots["default"]?.() ?? "Close";
94
+ return renderElement("button", {}, {
95
+ state: { open: dialog.open.value },
96
+ ref: (value) => {
97
+ element.value = value;
98
+ },
99
+ props: [{
100
+ type: "button",
101
+ children: content,
102
+ onClick: (event) => {
103
+ if (!event.defaultPrevented) dialog.setOpen(false);
104
+ }
105
+ }, attrs]
106
+ });
107
+ };
108
+ }
109
+ })
110
+ };
111
+ //#endregion
112
+ export { useAlertDialogContext as n, AlertDialog as t };
@@ -0,0 +1,29 @@
1
+ import { o as useMediaAttach } from "./context-C48Uabdu.js";
2
+ import { t as useSyncMediaProps } from "./useSyncMediaProps-C7F52G6b.js";
3
+ import { defineComponent, h, onBeforeUnmount, shallowRef, watch } from "vue";
4
+ //#region src/media/Audio.ts
5
+ const Audio = defineComponent({
6
+ name: "VideoJsAudio",
7
+ inheritAttrs: false,
8
+ setup(_, { attrs, expose, slots }) {
9
+ const setMedia = useMediaAttach();
10
+ const element = shallowRef(null);
11
+ watch(element, (value) => {
12
+ setMedia?.(value);
13
+ }, { immediate: true });
14
+ useSyncMediaProps(element, attrs);
15
+ onBeforeUnmount(() => {
16
+ const current = element.value;
17
+ setMedia?.((previous) => previous === current ? null : previous);
18
+ });
19
+ expose({ el: element });
20
+ return () => h("audio", {
21
+ ...attrs,
22
+ ref: ((value) => {
23
+ element.value = value;
24
+ })
25
+ }, slots["default"]?.());
26
+ }
27
+ });
28
+ //#endregion
29
+ export { Audio as t };
@@ -0,0 +1,7 @@
1
+ import { AudioHTMLAttributes } from "vue";
2
+
3
+ //#region src/media/Audio.d.ts
4
+ interface AudioProps extends AudioHTMLAttributes {}
5
+ declare const Audio: import("vue").DefineComponent<AudioProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<AudioProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ //#endregion
7
+ export { AudioProps as n, Audio as t };
@@ -0,0 +1,64 @@
1
+ import { a as useMedia } from "./context-C48Uabdu.js";
2
+ import { t as useMediaEvents } from "./useMediaEvents-Br7vzJ_l.js";
3
+ import { defineComponent, h, onBeforeUnmount, shallowRef } from "vue";
4
+ //#region src/ui/buffering-indicator/BufferingIndicator.ts
5
+ const BufferingIndicator = defineComponent({
6
+ name: "VideoJsBufferingIndicator",
7
+ inheritAttrs: false,
8
+ props: { delay: {
9
+ type: Number,
10
+ default: 500
11
+ } },
12
+ setup(props, { attrs, slots }) {
13
+ const media = useMedia();
14
+ const visible = shallowRef(false);
15
+ let timer = null;
16
+ function clearTimer() {
17
+ if (!timer) return;
18
+ clearTimeout(timer);
19
+ timer = null;
20
+ }
21
+ function queueShow() {
22
+ clearTimer();
23
+ timer = setTimeout(() => {
24
+ visible.value = true;
25
+ }, Math.max(0, props.delay));
26
+ }
27
+ function hide() {
28
+ clearTimer();
29
+ visible.value = false;
30
+ }
31
+ useMediaEvents(media, {
32
+ onDetach: hide,
33
+ bindings: () => [
34
+ {
35
+ type: "waiting",
36
+ listener: queueShow
37
+ },
38
+ {
39
+ type: "stalled",
40
+ listener: queueShow
41
+ },
42
+ {
43
+ type: "playing",
44
+ listener: hide
45
+ },
46
+ {
47
+ type: "canplay",
48
+ listener: hide
49
+ }
50
+ ]
51
+ });
52
+ onBeforeUnmount(() => {
53
+ hide();
54
+ });
55
+ return () => h("div", {
56
+ ...attrs,
57
+ hidden: !visible.value,
58
+ "aria-hidden": !visible.value,
59
+ "aria-busy": visible.value
60
+ }, slots["default"]?.());
61
+ }
62
+ });
63
+ //#endregion
64
+ export { BufferingIndicator as t };
@@ -0,0 +1,124 @@
1
+ import { a as useMedia } from "./context-C48Uabdu.js";
2
+ import { t as renderElement } from "./renderElement-D_UbvLk5.js";
3
+ import { computed, defineComponent, shallowRef, watch } from "vue";
4
+ //#region src/ui/factories/useCaptionTracks.ts
5
+ function getCaptionTrackList(media) {
6
+ const synthetic = media?.__vjsTextTracks;
7
+ if (Array.isArray(synthetic)) return synthetic.filter((track) => track.kind === "captions" || track.kind === "subtitles");
8
+ const textTracks = media?.textTracks;
9
+ if (!textTracks) return [];
10
+ return [...textTracks].filter((track) => track.kind === "captions" || track.kind === "subtitles");
11
+ }
12
+ function useCaptionTracks(media) {
13
+ const tracks = shallowRef([]);
14
+ const enabled = computed(() => tracks.value.some((track) => track.mode === "showing"));
15
+ const selectedIndex = computed(() => tracks.value.findIndex((track) => track.mode === "showing"));
16
+ const refresh = () => {
17
+ tracks.value = getCaptionTrackList(media.value).map((track, index) => ({
18
+ index,
19
+ kind: track.kind,
20
+ label: track.label,
21
+ language: track.language,
22
+ mode: track.mode
23
+ }));
24
+ };
25
+ const hideAll = () => {
26
+ const list = getCaptionTrackList(media.value);
27
+ for (const track of list) track.mode = "hidden";
28
+ refresh();
29
+ };
30
+ const show = (index) => {
31
+ const list = getCaptionTrackList(media.value);
32
+ if (list.length === 0) return;
33
+ const nextIndex = Math.max(0, Math.min(index, list.length - 1));
34
+ for (const [trackIndex, track] of list.entries()) track.mode = trackIndex === nextIndex ? "showing" : "hidden";
35
+ refresh();
36
+ };
37
+ const toggle = () => {
38
+ if (enabled.value) {
39
+ hideAll();
40
+ return;
41
+ }
42
+ const index = selectedIndex.value;
43
+ show(Math.max(index, 0));
44
+ };
45
+ watch(media, (node, _, onCleanup) => {
46
+ refresh();
47
+ if (!node) return;
48
+ const onChange = () => {
49
+ refresh();
50
+ };
51
+ node.addEventListener("loadedmetadata", onChange);
52
+ node.addEventListener("emptied", onChange);
53
+ const list = node.textTracks;
54
+ if (list?.addEventListener) {
55
+ list.addEventListener("change", onChange);
56
+ list.addEventListener("addtrack", onChange);
57
+ list.addEventListener("removetrack", onChange);
58
+ }
59
+ onCleanup(() => {
60
+ node.removeEventListener("loadedmetadata", onChange);
61
+ node.removeEventListener("emptied", onChange);
62
+ if (list?.removeEventListener) {
63
+ list.removeEventListener("change", onChange);
64
+ list.removeEventListener("addtrack", onChange);
65
+ list.removeEventListener("removetrack", onChange);
66
+ }
67
+ });
68
+ }, { immediate: true });
69
+ return {
70
+ tracks,
71
+ enabled,
72
+ selectedIndex,
73
+ refresh,
74
+ hideAll,
75
+ show,
76
+ toggle
77
+ };
78
+ }
79
+ //#endregion
80
+ //#region src/ui/captions-button/CaptionsButton.ts
81
+ const CaptionsButton = defineComponent({
82
+ name: "VideoJsCaptionsButton",
83
+ inheritAttrs: false,
84
+ props: {
85
+ onLabel: {
86
+ type: String,
87
+ default: "Captions on"
88
+ },
89
+ offLabel: {
90
+ type: String,
91
+ default: "Captions off"
92
+ }
93
+ },
94
+ setup(props, { attrs, slots, expose }) {
95
+ const element = shallowRef(null);
96
+ const captionTracks = useCaptionTracks(useMedia());
97
+ const label = computed(() => captionTracks.enabled.value ? props.onLabel : props.offLabel);
98
+ expose({ el: element });
99
+ return () => {
100
+ const content = slots["default"]?.() ?? label.value;
101
+ return renderElement("button", {}, {
102
+ state: {
103
+ enabled: captionTracks.enabled.value,
104
+ trackCount: captionTracks.tracks.value.length
105
+ },
106
+ ref: (value) => {
107
+ element.value = value;
108
+ },
109
+ props: [{
110
+ type: "button",
111
+ "aria-label": label.value,
112
+ "aria-pressed": captionTracks.enabled.value,
113
+ children: content,
114
+ onClick: (event) => {
115
+ if (event.defaultPrevented) return;
116
+ captionTracks.toggle();
117
+ }
118
+ }, attrs]
119
+ });
120
+ };
121
+ }
122
+ });
123
+ //#endregion
124
+ export { CaptionsButton as t };
@@ -0,0 +1,32 @@
1
+ import { a as useMedia } from "./context-C48Uabdu.js";
2
+ import { t as renderElement } from "./renderElement-D_UbvLk5.js";
3
+ import { defineComponent, shallowRef } from "vue";
4
+ //#region src/ui/cast-button/CastButton.ts
5
+ const CastButton = defineComponent({
6
+ name: "VideoJsCastButton",
7
+ inheritAttrs: false,
8
+ setup(_, { attrs, slots, expose }) {
9
+ const element = shallowRef(null);
10
+ const media = useMedia();
11
+ expose({ el: element });
12
+ return () => {
13
+ const content = slots["default"]?.() ?? "Cast";
14
+ return renderElement("button", {}, {
15
+ state: { hasMedia: Boolean(media.value) },
16
+ ref: (value) => {
17
+ element.value = value;
18
+ },
19
+ props: [{
20
+ type: "button",
21
+ children: content,
22
+ onClick: async (event) => {
23
+ if (event.defaultPrevented) return;
24
+ await media.value?.remote?.prompt?.();
25
+ }
26
+ }, attrs]
27
+ });
28
+ };
29
+ }
30
+ });
31
+ //#endregion
32
+ export { CastButton as t };
@@ -0,0 +1,33 @@
1
+ import { defineComponent, h, shallowRef } from "vue";
2
+ const Controls = {
3
+ Root: defineComponent({
4
+ name: "VideoJsControlsRoot",
5
+ inheritAttrs: false,
6
+ setup(_, { attrs, slots, expose }) {
7
+ const element = shallowRef(null);
8
+ expose({ el: element });
9
+ return () => h("div", {
10
+ ...attrs,
11
+ ref: ((value) => {
12
+ element.value = value;
13
+ })
14
+ }, slots["default"]?.());
15
+ }
16
+ }),
17
+ Group: defineComponent({
18
+ name: "VideoJsControlsGroup",
19
+ inheritAttrs: false,
20
+ setup(_, { attrs, slots, expose }) {
21
+ const element = shallowRef(null);
22
+ expose({ el: element });
23
+ return () => h("div", {
24
+ ...attrs,
25
+ ref: ((value) => {
26
+ element.value = value;
27
+ })
28
+ }, slots["default"]?.());
29
+ }
30
+ })
31
+ };
32
+ //#endregion
33
+ export { Controls as t };
@@ -0,0 +1,60 @@
1
+ import { a as useMedia } from "./context-C48Uabdu.js";
2
+ import { t as useMediaEvents } from "./useMediaEvents-Br7vzJ_l.js";
3
+ import { t as AlertDialog } from "./AlertDialog-BPntemoC.js";
4
+ import { computed, defineComponent, h, inject, provide, shallowRef } from "vue";
5
+ //#region src/ui/error-dialog/ErrorDialog.ts
6
+ const ERROR_DIALOG_KEY = Symbol("videojs-error-dialog");
7
+ function useErrorDialogContext() {
8
+ const context = inject(ERROR_DIALOG_KEY, null);
9
+ if (!context) throw new Error("ErrorDialog parts must be used within ErrorDialog.Root");
10
+ return context;
11
+ }
12
+ const Root = defineComponent({
13
+ name: "VideoJsErrorDialogRoot",
14
+ inheritAttrs: false,
15
+ setup(_, { attrs, slots }) {
16
+ const media = useMedia();
17
+ const lastError = shallowRef(null);
18
+ const sync = () => {
19
+ const node = media.value;
20
+ lastError.value = node ? node.error ?? null : null;
21
+ };
22
+ const clear = () => {
23
+ lastError.value = null;
24
+ };
25
+ useMediaEvents(media, {
26
+ onAttach: sync,
27
+ onDetach: clear,
28
+ bindings: () => [{
29
+ type: "error",
30
+ listener: sync
31
+ }, {
32
+ type: "loadeddata",
33
+ listener: clear
34
+ }]
35
+ });
36
+ const open = computed(() => Boolean(lastError.value));
37
+ provide(ERROR_DIALOG_KEY, { lastError });
38
+ return () => h(AlertDialog.Root, {
39
+ ...attrs,
40
+ open: open.value
41
+ }, slots);
42
+ }
43
+ });
44
+ const Description = defineComponent({
45
+ name: "VideoJsErrorDialogDescription",
46
+ inheritAttrs: false,
47
+ setup(_, { attrs, slots }) {
48
+ const { lastError } = useErrorDialogContext();
49
+ return () => h("p", { ...attrs }, slots["default"]?.() ?? lastError.value?.message ?? "Playback error");
50
+ }
51
+ });
52
+ const ErrorDialog = {
53
+ Root,
54
+ Popup: AlertDialog.Popup,
55
+ Title: AlertDialog.Title,
56
+ Description,
57
+ Close: AlertDialog.Close
58
+ };
59
+ //#endregion
60
+ export { useErrorDialogContext as n, ErrorDialog as t };