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,321 @@
1
+ <script lang="ts" setup>
2
+ import { computed, onBeforeUnmount, onMounted, ref } from "vue"
3
+ import type { LzcPlayer } from "@/components/Video/player"
4
+ import type { Subtitle } from "@/model"
5
+ import { fetchSubtitleCues, type SubtitleCue } from "./useSubtitleTrack"
6
+ import { useSubtitleRenderEngine } from "./useSubtitleRenderEngine"
7
+ import { useSubtitlePreference } from "@/components/Video/useSubtitlePreference"
8
+ import { isMobile } from "@/use/useUtils"
9
+
10
+ const props = defineProps<{
11
+ player: LzcPlayer
12
+ active?: boolean
13
+ }>()
14
+
15
+ const cues = ref<SubtitleCue[]>([])
16
+ const subtitle = ref<Subtitle>()
17
+ const hidden = ref(false)
18
+ const currentTime = ref(0)
19
+ const renderMode = ref<"none" | "vtt" | "ass-octopus">("none")
20
+ const viewportWidth = ref(0)
21
+ const iosPortraitFakeFullscreen = ref(false)
22
+ let loadToken = 0
23
+
24
+ const renderEngine = useSubtitleRenderEngine(props.player)
25
+ const { subtitleSizeLevel, getSubtitleSizePixels } = useSubtitlePreference()
26
+ const DEFAULT_VTT_BOTTOM_OFFSET_PERCENT = 0
27
+ const DEFAULT_VTT_TIME_OFFSET_SEC = 0
28
+ const IOS_PORTRAIT_FAKE_FULLSCREEN_BOTTOM_OFFSET_PX = -80
29
+ const CONTROLS_HIDDEN_BOTTOM_OFFSET_PX = -80
30
+ const VTT_SCALE_BREAKPOINTS = {
31
+ mobileMaxWidth: 900,
32
+ largeScreenMinWidth: 1600,
33
+ } as const
34
+ const VTT_SCALE_PROFILES = {
35
+ mobile: {
36
+ baseWidth: 550,
37
+ minScale: 0.55,
38
+ maxScale: 1.1,
39
+ },
40
+ desktop: {
41
+ baseWidth: 900,
42
+ minScale: 1.1,
43
+ maxScale: 1.6,
44
+ },
45
+ largeScreen: {
46
+ baseWidth: 1600,
47
+ minScale: 1.6,
48
+ maxScale: 2.8,
49
+ },
50
+ } as const
51
+
52
+ function getCurrentTime() {
53
+ const value = props.player.currentTime?.()
54
+ currentTime.value = Number.isFinite(value as number) ? (value as number) : 0
55
+ }
56
+
57
+ function updateHidden() {
58
+ hidden.value = props.player.isSubtitleHidden?.() ?? false
59
+ }
60
+
61
+ function parseCssPixel(value: string): number {
62
+ const parsed = Number.parseFloat(value)
63
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0
64
+ }
65
+
66
+ function updateFakeFullscreenState() {
67
+ iosPortraitFakeFullscreen.value =
68
+ isMobile() && props.player.hasClass("vjs-ios-fake-fullscreen-portrait")
69
+ }
70
+
71
+ function readFakeFullscreenWidth(): number {
72
+ if (!props.player.hasClass("vjs-ios-fake-fullscreen")) {
73
+ return 0
74
+ }
75
+ const container = props.player.$video().closest(".lzc-video-player")
76
+ if (container) {
77
+ const cssWidth =
78
+ getComputedStyle(container).getPropertyValue("--lzc-ios-fs-width")
79
+ const parsed = parseCssPixel(cssWidth)
80
+ if (parsed > 0) {
81
+ return parsed
82
+ }
83
+ }
84
+ const vv = window.visualViewport
85
+ const vw = vv?.width || window.innerWidth || 0
86
+ const vh = vv?.height || window.innerHeight || 0
87
+ return Math.max(vw, vh)
88
+ }
89
+
90
+ function updateViewportWidth() {
91
+ updateFakeFullscreenState()
92
+ const fakeFullscreenWidth = readFakeFullscreenWidth()
93
+ if (fakeFullscreenWidth > 0) {
94
+ viewportWidth.value = fakeFullscreenWidth
95
+ return
96
+ }
97
+ const dims = props.player.currentDimensions()
98
+ const next = Number.isFinite(dims.width) ? dims.width : 0
99
+ if (next > 0) {
100
+ viewportWidth.value = next
101
+ return
102
+ }
103
+ const fallback = props.player.$video()?.clientWidth || window.innerWidth || 0
104
+ viewportWidth.value = fallback > 0 ? fallback : 0
105
+ }
106
+
107
+ function computeVttScaleByViewportWidth(width: number): number {
108
+ let profile: typeof VTT_SCALE_PROFILES[keyof typeof VTT_SCALE_PROFILES] =
109
+ VTT_SCALE_PROFILES.desktop
110
+ if (width >= VTT_SCALE_BREAKPOINTS.largeScreenMinWidth) {
111
+ profile = VTT_SCALE_PROFILES.largeScreen
112
+ } else if (width < VTT_SCALE_BREAKPOINTS.mobileMaxWidth) {
113
+ profile = VTT_SCALE_PROFILES.mobile
114
+ }
115
+ if (!Number.isFinite(width) || width <= 0) {
116
+ return profile.minScale
117
+ }
118
+ const ratio = width / profile.baseWidth
119
+ return Math.min(profile.maxScale, Math.max(profile.minScale, ratio))
120
+ }
121
+
122
+ function getSubtitleVttUrl(current?: Subtitle): string {
123
+ if (!current) return ""
124
+ return (current.vtt_url || "").trim()
125
+ }
126
+
127
+ function resetOverlaySubtitleState() {
128
+ renderEngine.reset()
129
+ renderMode.value = "none"
130
+ cues.value = []
131
+ }
132
+
133
+ async function loadSubtitleCues() {
134
+ loadToken += 1
135
+ const currentToken = loadToken
136
+
137
+ const nextSubtitle = props.player.currentSubtitle?.()
138
+ const nextHidden = props.player.isSubtitleHidden?.() ?? false
139
+
140
+ subtitle.value = nextSubtitle
141
+ hidden.value = nextHidden
142
+
143
+ if (props.player.isUsingNativeSubtitleFallback?.() === true) {
144
+ resetOverlaySubtitleState()
145
+ return
146
+ }
147
+
148
+ const nextRenderMode = await renderEngine.selectMode(nextSubtitle, nextHidden)
149
+ if (currentToken !== loadToken) {
150
+ return
151
+ }
152
+
153
+ renderMode.value = nextRenderMode
154
+ if (renderMode.value !== "vtt") {
155
+ cues.value = []
156
+ return
157
+ }
158
+
159
+ if (!nextSubtitle || nextHidden) {
160
+ cues.value = []
161
+ return
162
+ }
163
+
164
+ const vttUrl = getSubtitleVttUrl(nextSubtitle)
165
+ if (!vttUrl) {
166
+ console.info(
167
+ "Current subtitle has no vtt_url and is skipped",
168
+ nextSubtitle,
169
+ )
170
+ cues.value = []
171
+ return
172
+ }
173
+
174
+ const parsed = await fetchSubtitleCues(vttUrl)
175
+ if (currentToken !== loadToken) {
176
+ return
177
+ }
178
+ cues.value = parsed
179
+ getCurrentTime()
180
+ }
181
+
182
+ const activeCues = computed(() => {
183
+ if (renderMode.value !== "vtt" || hidden.value || cues.value.length === 0) {
184
+ return []
185
+ }
186
+ const shifted = Math.max(0, currentTime.value + DEFAULT_VTT_TIME_OFFSET_SEC)
187
+ return cues.value.filter((cue) => shifted >= cue.start && shifted < cue.end)
188
+ })
189
+
190
+ const isControlsActive = computed(() => props.active !== false)
191
+
192
+ const layerStyle = computed(() => ({
193
+ bottom: !isControlsActive.value
194
+ ? `${CONTROLS_HIDDEN_BOTTOM_OFFSET_PX}px`
195
+ : iosPortraitFakeFullscreen.value
196
+ ? `${IOS_PORTRAIT_FAKE_FULLSCREEN_BOTTOM_OFFSET_PX}px`
197
+ : `${DEFAULT_VTT_BOTTOM_OFFSET_PERCENT}%`,
198
+ }))
199
+
200
+ const cueStyle = computed(() => {
201
+ const base = getSubtitleSizePixels(subtitleSizeLevel.value)
202
+ const scale = computeVttScaleByViewportWidth(viewportWidth.value)
203
+ const fontSize = Math.round(base * scale)
204
+ return {
205
+ fontSize: `${fontSize}px`,
206
+ }
207
+ })
208
+
209
+ const onTimeUpdate = () => getCurrentTime()
210
+ const onSeeked = () => getCurrentTime()
211
+ const onRateChange = () => getCurrentTime()
212
+ const onResize = () => updateViewportWidth()
213
+ const onOrientationChange = () => updateViewportWidth()
214
+ const onLoadedMetadata = () => updateViewportWidth()
215
+ const onFullscreenChange = () => {
216
+ updateViewportWidth()
217
+ if (
218
+ typeof window !== "undefined" &&
219
+ typeof window.requestAnimationFrame === "function"
220
+ ) {
221
+ window.requestAnimationFrame(() => {
222
+ updateViewportWidth()
223
+ })
224
+ }
225
+ }
226
+ const onSubtitleReady = () => {
227
+ void loadSubtitleCues()
228
+ }
229
+ const onSubtitleChange = () => {
230
+ void loadSubtitleCues()
231
+ }
232
+ const onSubtitleVisibilityChange = () => {
233
+ updateHidden()
234
+ void loadSubtitleCues()
235
+ }
236
+ const onOpenVideo = () => {
237
+ resetOverlaySubtitleState()
238
+ subtitle.value = undefined
239
+ updateViewportWidth()
240
+ void loadSubtitleCues()
241
+ }
242
+
243
+ onMounted(() => {
244
+ getCurrentTime()
245
+ updateViewportWidth()
246
+ void loadSubtitleCues()
247
+
248
+ window.addEventListener("resize", onResize)
249
+ window.addEventListener("orientationchange", onOrientationChange)
250
+ props.player.on("loadedmetadata", onLoadedMetadata)
251
+ props.player.on("fullscreenchange", onFullscreenChange)
252
+ props.player.on("timeupdate", onTimeUpdate)
253
+ props.player.on("seeked", onSeeked)
254
+ props.player.on("ratechange", onRateChange)
255
+ props.player.on("subtitleReady", onSubtitleReady)
256
+ props.player.on("subtitlechange", onSubtitleChange)
257
+ props.player.on("subtitlevisibilitychange", onSubtitleVisibilityChange)
258
+ props.player.on("openVideo", onOpenVideo)
259
+ })
260
+
261
+ onBeforeUnmount(() => {
262
+ window.removeEventListener("resize", onResize)
263
+ window.removeEventListener("orientationchange", onOrientationChange)
264
+ props.player.off("loadedmetadata", onLoadedMetadata)
265
+ props.player.off("fullscreenchange", onFullscreenChange)
266
+ props.player.off("timeupdate", onTimeUpdate)
267
+ props.player.off("seeked", onSeeked)
268
+ props.player.off("ratechange", onRateChange)
269
+ props.player.off("subtitleReady", onSubtitleReady)
270
+ props.player.off("subtitlechange", onSubtitleChange)
271
+ props.player.off("subtitlevisibilitychange", onSubtitleVisibilityChange)
272
+ props.player.off("openVideo", onOpenVideo)
273
+ renderEngine.dispose()
274
+ })
275
+ </script>
276
+
277
+ <template>
278
+ <div
279
+ v-if="activeCues.length > 0"
280
+ class="subtitle-layer"
281
+ :style="layerStyle"
282
+ aria-hidden="true"
283
+ >
284
+ <div
285
+ v-for="(cue, idx) in activeCues"
286
+ :key="`${cue.start}-${cue.end}-${idx}`"
287
+ class="subtitle-cue"
288
+ :style="cueStyle"
289
+ >
290
+ {{ cue.text }}
291
+ </div>
292
+ </div>
293
+ </template>
294
+
295
+ <style lang="scss" scoped>
296
+ .subtitle-layer {
297
+ position: absolute;
298
+ left: 0;
299
+ right: 0;
300
+ z-index: 2;
301
+ display: flex;
302
+ flex-direction: column;
303
+ align-items: center;
304
+ gap: 8px;
305
+ padding: 0 24px;
306
+ pointer-events: none;
307
+ }
308
+
309
+ .subtitle-cue {
310
+ max-width: min(90%, 1200px);
311
+ color: #fff;
312
+ text-align: center;
313
+ line-height: 1.4;
314
+ white-space: pre-wrap;
315
+ word-break: break-word;
316
+ text-shadow: 1px 0 0 rgba(0, 0, 0, 0.9), -1px 0 0 rgba(0, 0, 0, 0.9),
317
+ 0 1px 0 rgba(0, 0, 0, 0.9), 0 -1px 0 rgba(0, 0, 0, 0.9),
318
+ 1px 1px 0 rgba(0, 0, 0, 0.75), -1px 1px 0 rgba(0, 0, 0, 0.75),
319
+ 1px -1px 0 rgba(0, 0, 0, 0.75), -1px -1px 0 rgba(0, 0, 0, 0.75);
320
+ }
321
+ </style>
@@ -0,0 +1,253 @@
1
+ <script lang="ts" setup>
2
+ import { computed } from "vue"
3
+ import { useDialogPluginComponent } from "quasar"
4
+ import type { RemoteMediaPlayer } from "@lazycatcloud/sdk/dist/dlna/dlna"
5
+ import { devices, castDevice, castLoading, doPlayAction } from "./useCast"
6
+ import { t } from "@/i18n"
7
+ import type { LzcPlayer } from "@/components/Video/player"
8
+
9
+ const props = defineProps<{ player: LzcPlayer; inline?: boolean }>()
10
+ const emit = defineEmits([
11
+ // REQUIRED; need to specify some events that your
12
+ // component will emit through useDialogPluginComponent()
13
+ ...useDialogPluginComponent.emits,
14
+ "close",
15
+ ])
16
+ const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent()
17
+ const isInline = computed(() => props.inline === true)
18
+
19
+ const closeCastDialog = () => {
20
+ if (isInline.value) {
21
+ emit("close")
22
+ } else {
23
+ onDialogOK()
24
+ }
25
+ }
26
+
27
+ const onSelect = (d: RemoteMediaPlayer) => {
28
+ castDevice.value = d
29
+ doPlayAction(props.player, props.player.src(null)!)
30
+ .then(() => {
31
+ closeCastDialog()
32
+ })
33
+ .catch((err: any) => {
34
+ console.error(err)
35
+ })
36
+ }
37
+
38
+
39
+ props.player.on("dispose", () => {
40
+ if (isInline.value) {
41
+ emit("close")
42
+ } else {
43
+ onDialogHide()
44
+ }
45
+ })
46
+ </script>
47
+
48
+ <template>
49
+ <div class="lzc-video-player">
50
+ <div
51
+ v-if="isInline"
52
+ class="cast-inline-backdrop"
53
+ @click.self="emit('close')"
54
+ >
55
+ <div class="cast">
56
+ <div class="title">
57
+ <span>{{
58
+ t(
59
+ "src.components.video.components.lzc_overlay.cast.film_delivery",
60
+ "影片投送",
61
+ )
62
+ }}</span>
63
+ </div>
64
+ <div class="search">
65
+ <span>{{
66
+ t(
67
+ "src.components.video.components.lzc_overlay.cast.searching",
68
+ "正在搜索设备…",
69
+ )
70
+ }}</span>
71
+ </div>
72
+ <q-list class="list">
73
+ <q-item
74
+ clickable
75
+ v-for="d in devices"
76
+ :key="d.uuid"
77
+ @click="onSelect(d)"
78
+ >
79
+ <q-item-section avatar v-if="castDevice?.uuid === d.uuid">
80
+ <div class="device-icon">
81
+ <span v-if="castLoading" class="casting"></span>
82
+ <q-icon v-else name="svguse:#icon-选择.svg"></q-icon>
83
+ </div>
84
+ </q-item-section>
85
+ <q-item-section class="device-name">
86
+ {{ d.name }}
87
+ </q-item-section>
88
+ <q-item-section side v-if="castDevice?.lanRegion != ''">
89
+ <div class="device-icon">
90
+ <q-icon
91
+ name="svguse:#icon-box.svg"
92
+ v-if="castDevice?.lanRegion == 'box'"
93
+ ></q-icon>
94
+ <q-icon name="svguse:#icon-client.svg" v-else></q-icon>
95
+ </div>
96
+ </q-item-section>
97
+ </q-item>
98
+ </q-list>
99
+ </div>
100
+ </div>
101
+ <q-dialog v-else ref="dialogRef" @hide="onDialogHide">
102
+ <div class="cast">
103
+ <div class="title">
104
+ <span>{{
105
+ t(
106
+ "src.components.video.components.lzc_overlay.cast.film_delivery",
107
+ "影片投送",
108
+ )
109
+ }}</span>
110
+ </div>
111
+ <div class="search">
112
+ <span>{{
113
+ t(
114
+ "src.components.video.components.lzc_overlay.cast.searching",
115
+ "正在搜索设备…",
116
+ )
117
+ }}</span>
118
+ </div>
119
+ <q-list class="list">
120
+ <q-item
121
+ clickable
122
+ v-for="d in devices"
123
+ :key="d.uuid"
124
+ @click="onSelect(d)"
125
+ >
126
+ <q-item-section avatar v-if="castDevice?.uuid === d.uuid">
127
+ <div class="device-icon">
128
+ <span v-if="castLoading" class="casting"></span>
129
+ <q-icon v-else name="svguse:#icon-选择.svg"></q-icon>
130
+ </div>
131
+ </q-item-section>
132
+ <q-item-section class="device-name">
133
+ {{ d.name }}
134
+ </q-item-section>
135
+ <q-item-section side v-if="castDevice?.lanRegion != ''">
136
+ <div class="device-icon">
137
+ <q-icon
138
+ name="svguse:#icon-box.svg"
139
+ v-if="castDevice?.lanRegion == 'box'"
140
+ ></q-icon>
141
+ <q-icon name="svguse:#icon-client.svg" v-else></q-icon>
142
+ </div>
143
+ </q-item-section>
144
+ </q-item>
145
+ </q-list>
146
+ </div>
147
+ </q-dialog>
148
+ </div>
149
+ </template>
150
+
151
+ <style lang="scss" scoped>
152
+ @mixin bb() {
153
+ border-bottom: 0.05rem solid rgba(255, 255, 255, 0.1);
154
+ }
155
+ .cast {
156
+ min-width: 37.5rem;
157
+ height: 26.7rem;
158
+ background: linear-gradient(
159
+ 135deg,
160
+ rgba(255, 255, 255, 0.25) 0%,
161
+ rgba(255, 255, 255, 0.1) 100%
162
+ );
163
+ border-radius: 1.2rem;
164
+ backdrop-filter: blur(10px);
165
+ -webkit-backdrop-filter: blur(10px);
166
+ }
167
+ .title {
168
+ height: 5.6rem;
169
+
170
+ display: flex;
171
+ flex-direction: row;
172
+ justify-content: center;
173
+ align-items: center;
174
+
175
+ @include bb;
176
+
177
+ span {
178
+ height: 2.5rem;
179
+ font-size: 1.8rem;
180
+ font-weight: 500;
181
+ color: #ffffff;
182
+ line-height: 2.5rem;
183
+ }
184
+ }
185
+ .search {
186
+ height: 3.9rem;
187
+ display: flex;
188
+ flex-direction: row;
189
+ align-items: center;
190
+ padding: 0rem 2rem;
191
+
192
+ @include bb;
193
+
194
+ span {
195
+ height: 1.7rem;
196
+ font-size: 1.2rem;
197
+ font-weight: 400;
198
+ color: #ffffff;
199
+ line-height: 1.7rem;
200
+ }
201
+ }
202
+ .list {
203
+ > .q-item {
204
+ padding: 0rem 1.9rem;
205
+ height: 5.6rem;
206
+ align-items: center;
207
+ @include bb;
208
+ }
209
+
210
+ :deep(.q-item__section--avatar) {
211
+ min-width: 2.5rem;
212
+ }
213
+ }
214
+ .device-icon {
215
+ color: white;
216
+ width: 1.6rem;
217
+ }
218
+ .device-name {
219
+ height: 2rem;
220
+ font-size: 1.4rem;
221
+ font-weight: 500;
222
+ color: #ffffff;
223
+ line-height: 2rem;
224
+ }
225
+ .casting {
226
+ width: 16px;
227
+ height: 16px;
228
+ border-radius: 50%;
229
+ border-top: 3px solid #5f86ff;
230
+ border-right: 3px solid transparent;
231
+
232
+ display: block;
233
+ animation: rotation 1s linear infinite;
234
+ @keyframes rotation {
235
+ 0% {
236
+ transform: rotate(0deg);
237
+ }
238
+ 100% {
239
+ transform: rotate(360deg);
240
+ }
241
+ }
242
+ }
243
+
244
+ .cast-inline-backdrop {
245
+ position: absolute;
246
+ inset: 0;
247
+ background: rgba(0, 0, 0, 0.35);
248
+ display: flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ z-index: 50;
252
+ }
253
+ </style>