lzc-video-player 0.0.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.
Files changed (162) hide show
  1. package/.dockerignore +1 -0
  2. package/.eslintrc.cjs +18 -0
  3. package/.prettierrc.json +5 -0
  4. package/AGENTS.md +31 -0
  5. package/README.md +38 -0
  6. package/build.sh +10 -0
  7. package/demo/.vscode/extensions.json +3 -0
  8. package/demo/README.md +40 -0
  9. package/demo/env.d.ts +1 -0
  10. package/demo/index.html +13 -0
  11. package/demo/package-lock.json +2037 -0
  12. package/demo/package.json +25 -0
  13. package/demo/public/favicon.ico +0 -0
  14. package/demo/src/App.vue +25 -0
  15. package/demo/src/assets/base.css +70 -0
  16. package/demo/src/assets/logo.svg +1 -0
  17. package/demo/src/assets/main.css +33 -0
  18. package/demo/src/main.ts +8 -0
  19. package/demo/tsconfig.config.json +8 -0
  20. package/demo/tsconfig.json +16 -0
  21. package/demo/vite.config.ts +14 -0
  22. package/docs/progress-bar-style-analysis.md +87 -0
  23. package/env.d.ts +1 -0
  24. package/error_pages/502.html.tpl +13 -0
  25. package/i18next-parser.config.mjs +147 -0
  26. package/index.html +54 -0
  27. package/lazycat.png +0 -0
  28. package/lib/README.md +48 -0
  29. package/lib/package.json +22 -0
  30. package/lzc-build.local.yml +65 -0
  31. package/lzc-build.yml +65 -0
  32. package/lzc-manifest.yml +53 -0
  33. package/makefile +15 -0
  34. package/package.json +69 -0
  35. package/postcss.config.js +6 -0
  36. package/public/512x512.png +0 -0
  37. package/public/favicon.ico +0 -0
  38. package/public/languages/en/translation.json +125 -0
  39. package/public/languages/zh/translation.json +125 -0
  40. package/public/libass-wasm/4.1.0/default.woff2 +0 -0
  41. package/public/libass-wasm/4.1.0/subtitles-octopus-worker-legacy.js +40 -0
  42. package/public/libass-wasm/4.1.0/subtitles-octopus-worker.js +1 -0
  43. package/public/libass-wasm/4.1.0/subtitles-octopus-worker.wasm +0 -0
  44. package/public/libass-wasm/4.1.0/subtitles-octopus.js +1680 -0
  45. package/public/square-128x128.png +0 -0
  46. package/public/square-256x256.png +0 -0
  47. package/public/square-512x512.png +0 -0
  48. package/src/App.vue +18 -0
  49. package/src/assets/base.scss +104 -0
  50. package/src/assets/cloud.png +0 -0
  51. package/src/assets/logo.svg +1 -0
  52. package/src/components/Dialog/index.vue +96 -0
  53. package/src/components/MultipleEdit/choose.vue +39 -0
  54. package/src/components/PlayList/index.vue +521 -0
  55. package/src/components/Spectrum/index.vue +58 -0
  56. package/src/components/Video/NativeVideoPlayer.vue +748 -0
  57. package/src/components/Video/README.md +3 -0
  58. package/src/components/Video/clientPlayer.ts +348 -0
  59. package/src/components/Video/components/LzcModal/components/simpleList.vue +57 -0
  60. package/src/components/Video/components/LzcModal/list.vue +52 -0
  61. package/src/components/Video/components/LzcModal/playrate.vue +45 -0
  62. package/src/components/Video/components/LzcModal/resolution.vue +117 -0
  63. package/src/components/Video/components/LzcModal/subtitle.vue +499 -0
  64. package/src/components/Video/components/LzcModal/useModal.ts +18 -0
  65. package/src/components/Video/components/LzcOverlay/SubtitleLayer.vue +321 -0
  66. package/src/components/Video/components/LzcOverlay/cast.vue +253 -0
  67. package/src/components/Video/components/LzcOverlay/casting.vue +205 -0
  68. package/src/components/Video/components/LzcOverlay/error.vue +103 -0
  69. package/src/components/Video/components/LzcOverlay/helper.ts +81 -0
  70. package/src/components/Video/components/LzcOverlay/index.vue +99 -0
  71. package/src/components/Video/components/LzcOverlay/playing.vue +496 -0
  72. package/src/components/Video/components/LzcOverlay/playingButtons.vue +122 -0
  73. package/src/components/Video/components/LzcOverlay/playingLayout.vue +287 -0
  74. package/src/components/Video/components/LzcOverlay/useCast.ts +235 -0
  75. package/src/components/Video/components/LzcOverlay/useCommon.ts +41 -0
  76. package/src/components/Video/components/LzcOverlay/useOctopusRenderer.ts +230 -0
  77. package/src/components/Video/components/LzcOverlay/useSubtitleRenderEngine.ts +79 -0
  78. package/src/components/Video/components/LzcOverlay/useSubtitleTrack.ts +139 -0
  79. package/src/components/Video/components/useLzcCommon.ts +16 -0
  80. package/src/components/Video/directPlay.ts +345 -0
  81. package/src/components/Video/getSubtitleInfo.ts +42 -0
  82. package/src/components/Video/native/EventEmitter.ts +62 -0
  83. package/src/components/Video/native/NativeControls.vue +510 -0
  84. package/src/components/Video/native/NativeModal.vue +133 -0
  85. package/src/components/Video/native/NativePlayer.ts +913 -0
  86. package/src/components/Video/native/NativePlayer.vue +53 -0
  87. package/src/components/Video/native/index.ts +9 -0
  88. package/src/components/Video/native/native-player.css +183 -0
  89. package/src/components/Video/native/playerKey.ts +5 -0
  90. package/src/components/Video/native/useNativeCastMiddleware.ts +50 -0
  91. package/src/components/Video/native/useNativePlayer.ts +3 -0
  92. package/src/components/Video/native/useNativePlayerFullscreen.ts +44 -0
  93. package/src/components/Video/native/useNativePlayerHistory.ts +69 -0
  94. package/src/components/Video/native/useNativePlayerModal.ts +68 -0
  95. package/src/components/Video/native/useNativePlayerPlaylist.ts +67 -0
  96. package/src/components/Video/native/useNativePlayerState.ts +225 -0
  97. package/src/components/Video/player.ts +99 -0
  98. package/src/components/Video/theme/index.scss +291 -0
  99. package/src/components/Video/theme/videojs.css +1797 -0
  100. package/src/components/Video/useSource.ts +1431 -0
  101. package/src/components/Video/useSubtitlePreference.ts +66 -0
  102. package/src/components/Video/useWebview.ts +79 -0
  103. package/src/components/Video/videoFrame.ts +58 -0
  104. package/src/env.d.ts +3 -0
  105. package/src/i18n/README.md +392 -0
  106. package/src/i18n/index.ts +49 -0
  107. package/src/icons/Video_Player.svg +69 -0
  108. package/src/icons/box.svg +15 -0
  109. package/src/icons/client.svg +17 -0
  110. package/src/icons/logo.svg +28 -0
  111. package/src/icons//344/270/212/344/270/200/344/270/252.svg +6 -0
  112. package/src/icons//344/270/213/344/270/200/344/270/252.svg +4 -0
  113. package/src/icons//344/272/256/345/272/246.svg +13 -0
  114. package/src/icons//345/200/215/351/200/237.svg +14 -0
  115. package/src/icons//345/205/250/345/261/217.svg +16 -0
  116. package/src/icons//345/205/250/351/200/211_/345/267/262/351/200/211/344/270/255.svg +16 -0
  117. package/src/icons//345/205/250/351/200/211_/346/234/252/351/200/211/344/270/255.svg +15 -0
  118. package/src/icons//345/205/263/351/227/255/345/244/232/351/200/211.svg +14 -0
  119. package/src/icons//345/205/263/351/227/255/346/212/225/345/261/217.svg +11 -0
  120. package/src/icons//345/233/236/346/224/266/347/253/231.svg +15 -0
  121. package/src/icons//345/244/261/346/225/210.svg +17 -0
  122. package/src/icons//346/207/222/347/214/253/346/222/255/346/224/276/345/231/250-icon.png +0 -0
  123. package/src/icons//346/207/222/347/214/253/346/222/255/346/224/276/345/231/250.png +0 -0
  124. package/src/icons//346/212/225/345/261/217.svg +11 -0
  125. package/src/icons//346/212/225/351/200/201/344/270/255.jpg +0 -0
  126. package/src/icons//346/212/225/351/200/201/344/270/255.svg +21 -0
  127. package/src/icons//346/222/255/346/224/276.svg +3 -0
  128. package/src/icons//346/232/202/345/201/234.svg +4 -0
  129. package/src/icons//346/232/202/346/227/240.svg +21 -0
  130. package/src/icons//346/233/264/345/244/232/346/223/215/344/275/234.svg +11 -0
  131. package/src/icons//347/224/265/350/247/206.svg +18 -0
  132. package/src/icons//347/247/273/345/212/250/347/253/257_/350/203/214/346/231/257.webp +0 -0
  133. package/src/icons//350/203/214/346/231/257.png +0 -0
  134. package/src/icons//350/277/224/345/233/236.svg +13 -0
  135. package/src/icons//350/277/233/345/205/245/345/205/250/345/261/217.svg +13 -0
  136. package/src/icons//351/200/200/345/207/272/345/205/250/345/261/217.svg +15 -0
  137. package/src/icons//351/200/211/346/213/251.svg +15 -0
  138. package/src/icons//351/237/263/351/207/217.svg +13 -0
  139. package/src/index.d.ts +9 -0
  140. package/src/lzc-video-player.scss +7 -0
  141. package/src/lzc-video-player.ts +6 -0
  142. package/src/main.ts +62 -0
  143. package/src/model.ts +77 -0
  144. package/src/quasar-variables.sass +10 -0
  145. package/src/router/index.ts +74 -0
  146. package/src/stores/pinia.ts +3 -0
  147. package/src/stores/playlist.ts +146 -0
  148. package/src/use/useKeyBind.ts +61 -0
  149. package/src/use/useMultipleEdit.ts +60 -0
  150. package/src/use/useSdk.ts +5 -0
  151. package/src/use/useSubtitle.ts +39 -0
  152. package/src/use/useUtils.ts +22 -0
  153. package/src/use/useVideoFrame.ts +60 -0
  154. package/src/views/Home.ts +99 -0
  155. package/src/views/mobile/Home.vue +246 -0
  156. package/src/views/mobile/Player.vue +141 -0
  157. package/tailwind.config.js +15 -0
  158. package/tsconfig.config.json +8 -0
  159. package/tsconfig.json +20 -0
  160. package/vite.config.lib.ts +88 -0
  161. package/vite.config.ts +122 -0
  162. package/vue-shim.d.ts +4 -0
@@ -0,0 +1,499 @@
1
+ <script lang="ts" setup>
2
+ import { ref, onMounted, computed, onBeforeUnmount } from "vue"
3
+ import type { LzcPlayer } from "@/components/Video/player"
4
+ import type { Subtitle } from "@/model"
5
+ import { t } from "@/i18n"
6
+ import { isMobile } from "@/use/useUtils"
7
+ import {
8
+ useSubtitlePreference,
9
+ type SubtitleSizeLevel,
10
+ } from "@/components/Video/useSubtitlePreference"
11
+
12
+ const props = defineProps<{ player: LzcPlayer }>()
13
+ const subtitleList = ref<Subtitle[]>([])
14
+ const currentSubtitle = ref<Subtitle>()
15
+ const canvasRef = ref<HTMLCanvasElement>()
16
+ const subtitleHiddenState = ref(false)
17
+ const subtitleLoading = ref(false)
18
+ const { subtitleSizeLevel, setSubtitleSizeLevel } = useSubtitlePreference()
19
+
20
+ const maxTextWidth = 250
21
+
22
+ const internal = computed(() =>
23
+ subtitleList.value.filter((i) => !i.is_external),
24
+ )
25
+ const external = computed(() => subtitleList.value.filter((i) => i.is_external))
26
+
27
+ const maxSubtitleWidth = computed(() => {
28
+ const list = [...internal.value.map(getName), ...external.value.map(getName)].map(
29
+ calcTextWidth,
30
+ )
31
+ const max = Math.max(...list)
32
+ return Number.isFinite(max) ? max : 0
33
+ })
34
+
35
+ const style = computed(() => ({
36
+ width: maxSubtitleWidth.value > maxTextWidth ? "300px" : "",
37
+ }))
38
+
39
+ const list = computed(() =>
40
+ [
41
+ {
42
+ key: "internal",
43
+ label: t("src.components.video.components.lzc_modal.subtitle.internal"),
44
+ value: internal.value,
45
+ },
46
+ {
47
+ key: "external",
48
+ label: t("src.components.video.components.lzc_modal.subtitle.external"),
49
+ value: external.value,
50
+ },
51
+ ].filter((i) => i.value.length > 0),
52
+ )
53
+ const canToggleHiddenSubtitle = computed(() => subtitleList.value.length > 0)
54
+ const subtitleSizeOptions = computed<Array<{
55
+ key: SubtitleSizeLevel
56
+ label: string
57
+ }>>(() => [
58
+ {
59
+ key: "small",
60
+ label: t("src.components.video.components.lzc_modal.subtitle.size_small"),
61
+ },
62
+ {
63
+ key: "medium",
64
+ label: t("src.components.video.components.lzc_modal.subtitle.size_medium"),
65
+ },
66
+ {
67
+ key: "large",
68
+ label: t("src.components.video.components.lzc_modal.subtitle.size_large"),
69
+ },
70
+ ])
71
+
72
+ const externalSubtitleSliceEnd = computed(() =>
73
+ findCommonPrefixRange(external.value.map(getName)),
74
+ )
75
+
76
+ const onSelect = (obj: Subtitle) => {
77
+ if (!isRenderable(obj)) {
78
+ return
79
+ }
80
+ if (isActive(obj) && !isHiddenSub.value) {
81
+ const modal = props.player.lzcModal?.()
82
+ modal?.close()
83
+ return
84
+ }
85
+ props.player.setSubtitleHidden(false)
86
+ updateSubtitleHiddenState()
87
+ props.player.changeSubtitle(obj)
88
+ currentSubtitle.value = props.player.currentSubtitle()
89
+ const modal = props.player.lzcModal?.()
90
+ modal?.close()
91
+ }
92
+
93
+ const isActive = (item: Subtitle) => {
94
+ return (
95
+ currentSubtitle.value?.name === item.name &&
96
+ currentSubtitle.value?.stream_index === item.stream_index
97
+ )
98
+ }
99
+
100
+ function isRenderable(item: Subtitle): boolean {
101
+ return hasRenderableSubtitleSource(item) || hasLegacySubtitleTarget(item)
102
+ }
103
+
104
+ function hasRenderableSubtitleSource(item: Subtitle): boolean {
105
+ return !!(item.vtt_url || item.ass_url || "").trim()
106
+ }
107
+
108
+ function hasLegacySubtitleTarget(item: Subtitle): boolean {
109
+ if (hasRenderableSubtitleSource(item)) {
110
+ return false
111
+ }
112
+ return Number.isInteger(item.stream_index) && item.stream_index >= 0
113
+ }
114
+
115
+ function showUnavailableNote(item: Subtitle): boolean {
116
+ return !hasRenderableSubtitleSource(item) && !hasLegacySubtitleTarget(item)
117
+ }
118
+
119
+ function getName(item: Subtitle) {
120
+ const name = (item.name || "").trim()
121
+ const lang = (item.language || "").trim()
122
+ return name || lang || `${item.stream_index + 1}`
123
+ }
124
+
125
+ function findCommonPrefixRange(strings: string[]) {
126
+ if (strings.length === 0) return null
127
+
128
+ let minLen = Math.min(...strings.map((s) => s.length))
129
+ let end = -1
130
+
131
+ for (let i = 0; i < minLen; i++) {
132
+ const char = strings[0][i]
133
+ if (strings.every((s) => s[i] === char)) {
134
+ end = i
135
+ } else {
136
+ break
137
+ }
138
+ }
139
+
140
+ if (end === -1) return null
141
+ return { end }
142
+ }
143
+
144
+ function calcTextWidth(text: string) {
145
+ const canvas = canvasRef.value
146
+ if (!canvas) {
147
+ return 0
148
+ }
149
+ const ctx = canvas.getContext("2d")
150
+ if (!ctx) {
151
+ return 0
152
+ }
153
+ ctx.font = "14px Arial, Helvetica, sans-serif"
154
+ return ctx.measureText(text).width
155
+ }
156
+
157
+ function truncateToTwoLines(
158
+ text: string,
159
+ end: number,
160
+ maxWidth = maxTextWidth,
161
+ ) {
162
+ if (text.length === end + 1) {
163
+ end -= 3
164
+ end = Math.max(0, end)
165
+ }
166
+ let width = 0
167
+ let lines = 1
168
+ let result = ""
169
+
170
+ for (let i = text.length - 1; i > end; i--) {
171
+ const ch = text[i]
172
+ const w = calcTextWidth(ch)
173
+
174
+ if (width + w > maxWidth) {
175
+ lines++
176
+ if (lines > 2) {
177
+ return result
178
+ }
179
+ width = 0
180
+ }
181
+
182
+ width += w
183
+ result = ch + result
184
+ }
185
+
186
+ const endSlice = result
187
+ result = ""
188
+
189
+ for (let i = 0; i < end + 1; i++) {
190
+ const ch = text[i]
191
+ const w = calcTextWidth(ch)
192
+
193
+ if (width + w > maxWidth) {
194
+ lines++
195
+ if (lines > 2) break
196
+ width = 0
197
+ }
198
+
199
+ width += w
200
+ result += ch
201
+ }
202
+ result += endSlice
203
+ const total = result.length
204
+ if (text.length === total) {
205
+ return result
206
+ }
207
+ const replaceStr = "..."
208
+ const endSize = text.length - end - 1
209
+ return (
210
+ result.slice(0, total - endSize - replaceStr.length) + replaceStr + endSlice
211
+ )
212
+ }
213
+
214
+ function truncateExternalSubtitle(subtitle: Subtitle) {
215
+ const range = externalSubtitleSliceEnd.value
216
+ const name = getName(subtitle)
217
+ if (!range) {
218
+ return name
219
+ }
220
+ const { end } = range
221
+ return truncateToTwoLines(name, end)
222
+ }
223
+
224
+ function loadSubTitle() {
225
+ subtitleLoading.value = props.player.isSubtitleLoading?.() ?? false
226
+ if (props.player.getAvaliableSubtitles) {
227
+ subtitleList.value = props.player.getAvaliableSubtitles() as Subtitle[]
228
+ }
229
+ if (props.player.currentSubtitle) {
230
+ currentSubtitle.value = props.player.currentSubtitle()
231
+ }
232
+ updateSubtitleHiddenState()
233
+ }
234
+
235
+ const isHiddenSub = computed(() => {
236
+ if (subtitleHiddenState.value !== undefined) {
237
+ return subtitleHiddenState.value
238
+ }
239
+ if (props.player.isSubtitleHidden) {
240
+ return props.player.isSubtitleHidden()
241
+ }
242
+ return false
243
+ })
244
+
245
+ const updateSubtitleHiddenState = () => {
246
+ if (props.player.isSubtitleHidden) {
247
+ subtitleHiddenState.value = props.player.isSubtitleHidden()
248
+ } else {
249
+ subtitleHiddenState.value = false
250
+ }
251
+ }
252
+
253
+ const hiddenSub = () => {
254
+ if (!canToggleHiddenSubtitle.value) {
255
+ return
256
+ }
257
+ if (props.player.isSubtitleHidden?.() === true) {
258
+ const modal = props.player.lzcModal?.()
259
+ modal?.close()
260
+ return
261
+ }
262
+ if (props.player.toggleSubtitleVisibility) {
263
+ props.player.toggleSubtitleVisibility()
264
+ }
265
+ updateSubtitleHiddenState()
266
+ currentSubtitle.value = props.player.currentSubtitle()
267
+ const modal = props.player.lzcModal?.()
268
+ modal?.close()
269
+ }
270
+
271
+ const onHiddenSubClick = () => {
272
+ if (!canToggleHiddenSubtitle.value) return
273
+ hiddenSub()
274
+ }
275
+
276
+ const onSubtitleSizeSelect = (level: SubtitleSizeLevel) => {
277
+ if (subtitleSizeLevel.value === level) return
278
+ setSubtitleSizeLevel(level)
279
+ }
280
+
281
+ const handleSubtitleVisibilityChange = () => {
282
+ updateSubtitleHiddenState()
283
+ currentSubtitle.value = props.player.currentSubtitle()
284
+ }
285
+
286
+ const handleSubtitleChange = () => {
287
+ updateSubtitleHiddenState()
288
+ currentSubtitle.value = props.player.currentSubtitle()
289
+ }
290
+
291
+ const handleSubtitleLoadingChange = () => {
292
+ loadSubTitle()
293
+ }
294
+
295
+ onMounted(() => {
296
+ loadSubTitle()
297
+
298
+ props.player.on("subtitleReady", loadSubTitle)
299
+ props.player.on("subtitleloadingchange", handleSubtitleLoadingChange)
300
+ props.player.on("subtitlevisibilitychange", handleSubtitleVisibilityChange)
301
+ props.player.on("subtitlechange", handleSubtitleChange)
302
+ })
303
+
304
+ onBeforeUnmount(() => {
305
+ props.player.off("subtitleReady", loadSubTitle)
306
+ props.player.off("subtitleloadingchange", handleSubtitleLoadingChange)
307
+ props.player.off("subtitlevisibilitychange", handleSubtitleVisibilityChange)
308
+ props.player.off("subtitlechange", handleSubtitleChange)
309
+ })
310
+ </script>
311
+
312
+ <template>
313
+ <div class="subtitle" :style="style">
314
+ <canvas class="canvas" ref="canvasRef"></canvas>
315
+
316
+ <div class="group" :class="list.length ? '' : 'disabled'">
317
+ <div
318
+ class="item"
319
+ :class="[
320
+ isHiddenSub ? 'active' : '',
321
+ canToggleHiddenSubtitle ? '' : 'item-disabled',
322
+ ]"
323
+ @click="onHiddenSubClick"
324
+ >
325
+ <div class="line-clamp-2">
326
+ {{
327
+ t(
328
+ "src.components.video.components.lzc_modal.subtitle.hidden_subtitle",
329
+ )
330
+ }}
331
+ <span v-if="!isMobile() && canToggleHiddenSubtitle">/ Alt+H</span>
332
+ </div>
333
+ </div>
334
+ </div>
335
+
336
+ <div class="group">
337
+ <div class="label">
338
+ {{ t("src.components.video.components.lzc_modal.subtitle.subtitle_size") }}
339
+ </div>
340
+ <div class="subtitle-size-list">
341
+ <div
342
+ v-for="item in subtitleSizeOptions"
343
+ :key="item.key"
344
+ class="subtitle-size-item"
345
+ :class="subtitleSizeLevel === item.key ? 'active' : ''"
346
+ @click="onSubtitleSizeSelect(item.key)"
347
+ >
348
+ {{ item.label }}
349
+ </div>
350
+ </div>
351
+ </div>
352
+
353
+ <template v-if="list.length">
354
+ <div v-for="{ key, label, value } in list" :key="key" class="group">
355
+ <div class="label">{{ label }}</div>
356
+ <div
357
+ v-for="(item, index) in value"
358
+ :key="`${key}-${index}`"
359
+ class="item"
360
+ :class="[
361
+ isHiddenSub ? '' : isActive(item) ? 'active' : '',
362
+ isRenderable(item) ? '' : 'item-disabled',
363
+ ]"
364
+ @click="onSelect(item)"
365
+ >
366
+ <div v-if="key === 'internal'" class="line-clamp-2">
367
+ {{ getName(item) }}
368
+ <span v-if="showUnavailableNote(item)" class="item-note">(VTT unavailable)</span>
369
+ </div>
370
+ <div v-else>
371
+ {{ truncateExternalSubtitle(item) }}
372
+ <span v-if="showUnavailableNote(item)" class="item-note">(VTT unavailable)</span>
373
+ </div>
374
+ </div>
375
+ </div>
376
+ </template>
377
+
378
+ <div v-else-if="subtitleLoading" class="group">
379
+ <div class="item">
380
+ <div class="line-clamp-2 item disabled">
381
+ {{ t("src.components.video.components.lzc_modal.subtitle.loading") }}
382
+ </div>
383
+ </div>
384
+ </div>
385
+
386
+ <div v-else class="group">
387
+ <div class="item">
388
+ <div class="line-clamp-2 item disabled">
389
+ {{
390
+ t("src.components.video.components.lzc_modal.subtitle.no_subtitle")
391
+ }}
392
+ </div>
393
+ </div>
394
+ </div>
395
+ </div>
396
+ </template>
397
+
398
+ <style lang="scss" scoped>
399
+ .canvas {
400
+ position: absolute;
401
+ transform: scale(0);
402
+ }
403
+
404
+ .subtitle {
405
+ background: rgba(0, 0, 0, 0.5);
406
+ backdrop-filter: blur(20px);
407
+ -webkit-backdrop-filter: blur(20px);
408
+ border-radius: 4px;
409
+ padding: 16px 8px;
410
+ font-size: 14px;
411
+ line-height: 20px;
412
+ border: 1px solid rgba(255, 255, 255, 0.3);
413
+ overflow-y: auto;
414
+ max-height: 400px;
415
+
416
+ @media (orientation: landscape) and (height < 600px) {
417
+ max-height: 200px;
418
+ }
419
+ }
420
+
421
+ .group:not(:last-child) {
422
+ margin-bottom: 16px;
423
+ }
424
+
425
+ .label {
426
+ padding: 0 16px;
427
+ margin-bottom: 8px;
428
+ color: #828283;
429
+ font-size: 13px;
430
+ }
431
+
432
+ .item {
433
+ padding: 8px 16px;
434
+ word-break: break-all;
435
+
436
+ &:not(:last-child) {
437
+ margin-bottom: 4px;
438
+ }
439
+
440
+ &:not(.disabled):not(.item-disabled):hover,
441
+ &.active {
442
+ background: #ffffff33;
443
+ cursor: pointer;
444
+ border-radius: 4px;
445
+ color: #5f86ff;
446
+ }
447
+
448
+ &.disabled {
449
+ padding: 0;
450
+ }
451
+ }
452
+
453
+ .line-clamp-2 {
454
+ overflow: hidden;
455
+ display: -webkit-box;
456
+ -webkit-box-orient: vertical;
457
+ -webkit-line-clamp: 2;
458
+ }
459
+
460
+ .item.item-disabled {
461
+ color: #828283;
462
+ cursor: not-allowed;
463
+ }
464
+
465
+ .item-note {
466
+ margin-left: 4px;
467
+ font-size: 12px;
468
+ color: #b7b7b7;
469
+ }
470
+
471
+ .subtitle-size-list {
472
+ display: flex;
473
+ align-items: center;
474
+ gap: 4px;
475
+ padding: 0 16px;
476
+ }
477
+
478
+ .subtitle-size-item {
479
+ flex: 1 1 0;
480
+ min-width: 72px;
481
+ text-align: center;
482
+ padding: 6px 0;
483
+ border-radius: 4px;
484
+ cursor: pointer;
485
+ color: #c5c5c5;
486
+ background: rgba(255, 255, 255, 0.08);
487
+ user-select: none;
488
+ white-space: nowrap;
489
+
490
+ &:hover {
491
+ background: rgba(255, 255, 255, 0.18);
492
+ }
493
+
494
+ &.active {
495
+ color: #5f86ff;
496
+ background: #ffffff33;
497
+ }
498
+ }
499
+ </style>
@@ -0,0 +1,18 @@
1
+ import { ref } from "vue"
2
+ export const activePage = ref("")
3
+ export const menuPosition = ref({
4
+ right: 0,
5
+ bottom: 0
6
+ })
7
+
8
+ export function setMenuPosition(event: { target: HTMLElement}) {
9
+ const { target } = event
10
+ const container = target.closest('.lzc-video-player .video-js')
11
+ if (!(container instanceof HTMLElement)) {
12
+ return
13
+ }
14
+ const { right: containerRight, bottom: containerBottom } = container.getBoundingClientRect()
15
+ const { width, top, right } = target.getBoundingClientRect()
16
+ menuPosition.value.right = containerRight - right + width / 2
17
+ menuPosition.value.bottom = containerBottom - top
18
+ }