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,287 @@
1
+ <script lang="ts" setup>
2
+ import { ref, computed, onMounted, onUnmounted, watch } from "vue"
3
+ import { AppCommon } from "@lazycatcloud/sdk/dist/extentions/app_common"
4
+ import { useVideoName } from "./useCommon"
5
+ import PlayingButtons from "./playingButtons.vue"
6
+ import { isMobile } from "@/use/useUtils"
7
+ import type { LzcPlayer } from "@/components/Video/player"
8
+
9
+ type AppCommonWithStatusBar = typeof AppCommon & {
10
+ SetStatusBarHidden?: (hidden: boolean) => void | Promise<void>
11
+ }
12
+
13
+ const appCommon = AppCommon as AppCommonWithStatusBar
14
+ const props = defineProps<{
15
+ player: LzcPlayer
16
+ active?: boolean
17
+ directMode?: boolean
18
+ }>()
19
+ const back = () => {
20
+ if (isMobile() && props.player.isFullscreen()) {
21
+ props.player.exitFullscreen()
22
+ } else {
23
+ props.player.trigger({ type: "back" })
24
+ }
25
+ }
26
+ const { videoName } = useVideoName(props.player)
27
+
28
+ const showTopBar = ref(props.player.options_.showTopBar)
29
+ const isPlaying = ref(!props.player.paused())
30
+ const isActive = computed(() => props.active !== false)
31
+ const isFakeFullscreen = ref(false)
32
+ let fakeFullscreenSyncTimer: ReturnType<typeof setTimeout> | null = null
33
+ const overlayClass = computed(() => ({
34
+ "vjs-has-started": true,
35
+ "vjs-user-inactive": !isActive.value,
36
+ "vjs-playing": isPlaying.value,
37
+ }))
38
+ const topBarVisible = computed(() => {
39
+ if (!showTopBar.value) return false
40
+ return !isPlaying.value || isActive.value
41
+ })
42
+ const shouldHideStatusBar = computed(
43
+ () => isFakeFullscreen.value || !topBarVisible.value,
44
+ )
45
+ function syncFakeFullscreenState() {
46
+ isFakeFullscreen.value = props.player.hasClass("vjs-ios-fake-fullscreen")
47
+ }
48
+ function scheduleFakeFullscreenStateSync() {
49
+ if (fakeFullscreenSyncTimer) {
50
+ clearTimeout(fakeFullscreenSyncTimer)
51
+ }
52
+ fakeFullscreenSyncTimer = setTimeout(() => {
53
+ fakeFullscreenSyncTimer = null
54
+ syncFakeFullscreenState()
55
+ }, 280)
56
+ }
57
+ const onFullscreenChange = () => {
58
+ if (props.player.isFullscreen()) {
59
+ // 全屏情况下,如果 showTopBar 为 false,将移动端强制展示 topbar,PC 端不显示
60
+ showTopBar.value = props.player.options_.showTopBar || isMobile()
61
+ } else {
62
+ showTopBar.value = props.player.options_.showTopBar
63
+ }
64
+ syncFakeFullscreenState()
65
+ if (
66
+ !props.player.isFullscreen() &&
67
+ props.player.hasClass("vjs-ios-fake-fullscreen")
68
+ ) {
69
+ scheduleFakeFullscreenStateSync()
70
+ }
71
+ }
72
+
73
+ const onPlay = () => {
74
+ isPlaying.value = true
75
+ }
76
+ const onPause = () => {
77
+ isPlaying.value = false
78
+ }
79
+
80
+ function setStatusBarHidden(hidden: boolean) {
81
+ try {
82
+ void appCommon.SetStatusBarHidden?.(hidden)
83
+ } catch {
84
+ // Older SDK/client builds may not expose this bridge yet.
85
+ }
86
+ }
87
+
88
+ onMounted(() => {
89
+ onFullscreenChange()
90
+ props.player.on("fullscreenchange", onFullscreenChange)
91
+ props.player.on("play", onPlay)
92
+ props.player.on("pause", onPause)
93
+ props.player.on("ended", onPause)
94
+ })
95
+
96
+ watch(
97
+ shouldHideStatusBar,
98
+ (hidden) => {
99
+ setStatusBarHidden(hidden)
100
+ },
101
+ { immediate: true },
102
+ )
103
+
104
+ onUnmounted(() => {
105
+ if (fakeFullscreenSyncTimer) {
106
+ clearTimeout(fakeFullscreenSyncTimer)
107
+ fakeFullscreenSyncTimer = null
108
+ }
109
+ setStatusBarHidden(false)
110
+ props.player.off("fullscreenchange", onFullscreenChange)
111
+ props.player.off("play", onPlay)
112
+ props.player.off("pause", onPause)
113
+ props.player.off("ended", onPause)
114
+ })
115
+ </script>
116
+
117
+ <template>
118
+ <div class="overlay horizontal" :class="overlayClass">
119
+ <div
120
+ data-no-play-toggle
121
+ class="vjs-overlay-padding top-space"
122
+ :class="showTopBar ? '' : 'invisible'"
123
+ >
124
+ <q-icon
125
+ data-no-play-toggle
126
+ @click="back"
127
+ name="svguse:#icon-返回.svg"
128
+ size="14px"
129
+ ></q-icon>
130
+ <span class="title">{{ videoName }}</span>
131
+ </div>
132
+
133
+ <div class="overlay-center">
134
+ <div
135
+ data-no-play-toggle
136
+ class="vjs-overlay-padding vertical-icons"
137
+ >
138
+ <PlayingButtons
139
+ :player="player"
140
+ :direct-mode="props.directMode"
141
+ ></PlayingButtons>
142
+ </div>
143
+ <slot></slot>
144
+ </div>
145
+ <div class="vjs-overlay-padding bottom-space"></div>
146
+ </div>
147
+
148
+ <div class="overlay vertical" :class="overlayClass">
149
+ <div
150
+ data-no-play-toggle
151
+ class="vjs-overlay-padding top-space"
152
+ :class="showTopBar ? '' : 'invisible'"
153
+ >
154
+ <q-icon
155
+ data-no-play-toggle
156
+ @click="back"
157
+ name="svguse:#icon-返回.svg"
158
+ size="14px"
159
+ ></q-icon>
160
+ <span class="title">{{ videoName }}</span>
161
+ </div>
162
+ <div class="overlay-center">
163
+ <div data-no-play-toggle class="vjs-overlay-padding vertical-icons">
164
+ <PlayingButtons
165
+ :player="player"
166
+ :direct-mode="props.directMode"
167
+ ></PlayingButtons>
168
+ </div>
169
+ <slot></slot>
170
+ </div>
171
+ <div class="vjs-overlay-padding bottom-space"></div>
172
+ </div>
173
+ </template>
174
+
175
+ <style lang="scss" scoped>
176
+ .overlay {
177
+ height: 100%;
178
+ width: 100%;
179
+
180
+ display: flex;
181
+ flex-direction: column;
182
+ }
183
+ .top-space {
184
+ --lzc-player-top-padding: max(4rem, var(--lzc-safe-area-inset-top));
185
+
186
+ visibility: visible;
187
+ display: flex;
188
+ height: calc(4.2rem + var(--lzc-player-top-padding));
189
+ flex-direction: row;
190
+ align-items: center;
191
+ padding-top: var(--lzc-player-top-padding);
192
+ padding-left: calc(20px + var(--lzc-player-safe-area-left, 0px));
193
+ padding-right: calc(20px + var(--lzc-player-safe-area-right, 0px));
194
+ padding-bottom: 2rem;
195
+ background: linear-gradient(360deg, rgba(0, 0, 0, 0) 0%, #000000 100%);
196
+ opacity: 1;
197
+ gap: 1.4rem;
198
+ transition:
199
+ height 260ms cubic-bezier(0.22, 0.61, 0.36, 1),
200
+ padding 260ms cubic-bezier(0.22, 0.61, 0.36, 1);
201
+
202
+ .q-icon {
203
+ cursor: pointer;
204
+ }
205
+
206
+ .title {
207
+ flex: 1;
208
+ height: 2.2rem;
209
+ font-size: 1.6rem;
210
+ font-weight: 500;
211
+ color: #ffffff;
212
+ line-height: 2.2rem;
213
+
214
+ white-space: nowrap;
215
+ overflow: hidden;
216
+ text-overflow: ellipsis;
217
+ }
218
+ }
219
+ .overlay-center {
220
+ flex: 1;
221
+ display: flex;
222
+ flex-direction: column;
223
+ position: relative;
224
+ }
225
+ .vertical-icons {
226
+ position: absolute;
227
+ bottom: calc(3rem + var(--lzc-safe-area-inset-bottom));
228
+ right: var(--lzc-player-safe-area-right, 0px);
229
+
230
+ visibility: visible;
231
+ display: flex;
232
+ flex-direction: column;
233
+ gap: 8px;
234
+ transition:
235
+ right 260ms cubic-bezier(0.22, 0.61, 0.36, 1),
236
+ bottom 260ms cubic-bezier(0.22, 0.61, 0.36, 1);
237
+ }
238
+
239
+ .bottom-space {
240
+ height: calc(9rem + var(--lzc-safe-area-inset-bottom));
241
+ visibility: visible;
242
+ background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000000 100%);
243
+ opacity: 1;
244
+ transition: height 260ms cubic-bezier(0.22, 0.61, 0.36, 1);
245
+ }
246
+ .vjs-has-started.vjs-user-inactive.vjs-playing {
247
+ .top-space {
248
+ visibility: hidden;
249
+ }
250
+ .bottom-space {
251
+ visibility: hidden;
252
+ }
253
+ .vertical-icons {
254
+ visibility: hidden;
255
+ }
256
+ }
257
+
258
+ @media screen and (min-width: 667px) {
259
+ .horizontal {
260
+ display: flex;
261
+ }
262
+ .vertical {
263
+ display: none;
264
+ }
265
+ }
266
+
267
+ @media screen and (max-width: 666px) {
268
+ .horizontal {
269
+ display: none;
270
+ }
271
+ .vertical {
272
+ display: flex;
273
+ }
274
+ }
275
+
276
+ .invisible {
277
+ visibility: hidden;
278
+ }
279
+
280
+ @media (prefers-reduced-motion: reduce) {
281
+ .top-space,
282
+ .vertical-icons,
283
+ .bottom-space {
284
+ transition: none;
285
+ }
286
+ }
287
+ </style>
@@ -0,0 +1,235 @@
1
+ import sdk from "@/use/useSdk"
2
+ import { ref, onMounted, onUnmounted, type Ref } from "vue"
3
+ import type { Subscription } from "rxjs"
4
+ import {
5
+ type RMPStatus,
6
+ type RemoteMediaPlayer,
7
+ RMPStatus_Status,
8
+ DoActionRequest_Action,
9
+ type DoActionRequest,
10
+ } from "@lazycatcloud/sdk/dist/dlna/dlna"
11
+ import { parseSecond } from "./useCommon"
12
+ import { formatTime } from "../useLzcCommon"
13
+ import { t } from "@/i18n"
14
+ import type { LzcPlayer } from "@/components/Video/player"
15
+
16
+ export const devices: Ref<RemoteMediaPlayer[]> = ref([])
17
+ export const castSubscribe: Ref<Subscription | null> = ref(null)
18
+ export const castDevice: Ref<RemoteMediaPlayer | null> = ref(null)
19
+ export const castLoading: Ref<boolean> = ref(false)
20
+
21
+ let _posInfoId: ReturnType<typeof setTimeout>
22
+ let _castSessionId = 0
23
+
24
+ export function useRefresh() {
25
+ let _refreshId: ReturnType<typeof setTimeout>
26
+ let _close = false
27
+ async function refresh() {
28
+ if (_close) {
29
+ return
30
+ }
31
+ try {
32
+ const res = await sdk.rmp.ScanRMP({})
33
+ devices.value = res.remoteMediaPlayers
34
+ } finally {
35
+ _refreshId = setTimeout(() => {
36
+ if (_refreshId) {
37
+ clearTimeout(_refreshId)
38
+ }
39
+ refresh()
40
+ }, 3000)
41
+ }
42
+ }
43
+
44
+ function unrefresh() {
45
+ if (_refreshId) {
46
+ clearTimeout(_refreshId)
47
+ }
48
+ _close = true
49
+ }
50
+
51
+ onMounted(refresh)
52
+ onUnmounted(unrefresh)
53
+ }
54
+
55
+ function cleanupCastSession(options?: { clearDevice?: boolean }) {
56
+ castSubscribe.value?.unsubscribe()
57
+ castSubscribe.value = null
58
+ if (options?.clearDevice === true) {
59
+ castDevice.value = null
60
+ }
61
+ castLoading.value = false
62
+ _castSessionId += 1
63
+ if (_posInfoId) {
64
+ clearTimeout(_posInfoId)
65
+ }
66
+ }
67
+
68
+ export function detachCastSession() {
69
+ cleanupCastSession({ clearDevice: false })
70
+ }
71
+
72
+ function subscribe(player: LzcPlayer) {
73
+ if (!castDevice.value) {
74
+ throw t(
75
+ "src.components.video.components.lzc_overlay.use_cast.error_not_device",
76
+ "没有选择投屏设备",
77
+ )
78
+ }
79
+ const initTime = player.currentTime()!
80
+ player.trigger({ type: "startCasting" })
81
+ _castSessionId += 1
82
+ const sessionId = _castSessionId
83
+
84
+ updatePosInfo(player, sessionId)
85
+
86
+ // 因为 subscribe 返回的一个消息为 status 3, 所以这里忽略第一个信息
87
+ let isFirst = false
88
+ const obs = sdk.rmp.Subscribe({ playerUuid: castDevice.value.uuid })
89
+ const wrapNext = (value: RMPStatus): void => {
90
+ if (!isFirst) {
91
+ isFirst = true
92
+ void doSeekAction(initTime).catch((err: any) => {
93
+ console.error(err)
94
+ })
95
+ return
96
+ }
97
+
98
+ switch (value.status) {
99
+ case RMPStatus_Status.Paused:
100
+ player.trigger({ type: "CastPause" })
101
+ player.trigger({ type: "pause" })
102
+ break
103
+ case RMPStatus_Status.Playing:
104
+ player.trigger({ type: "CastPlay" })
105
+ player.trigger({ type: "play" })
106
+ break
107
+ case RMPStatus_Status.Stopped:
108
+ cleanupCastSession({ clearDevice: true })
109
+ player.trigger({ type: "closeCasting" })
110
+ break
111
+ }
112
+ }
113
+ const wrapError = (err: any): void => {
114
+ console.error(err)
115
+ player.trigger({ type: "CastError" })
116
+ cleanupCastSession({ clearDevice: true })
117
+ }
118
+ const wrapComplete = () => {
119
+ console.log("subscribe complete!")
120
+ }
121
+ const sub = obs.subscribe({
122
+ next: wrapNext,
123
+ error: wrapError,
124
+ complete: wrapComplete,
125
+ })
126
+ // @ts-ignore
127
+ castSubscribe.value = sub
128
+ }
129
+
130
+ async function doAction(req: Partial<DoActionRequest>) {
131
+ if (castDevice.value && castDevice.value.uuid !== "") {
132
+ req.playerUuid = castDevice.value.uuid
133
+ return sdk.rmp.DoAction(req)
134
+ }
135
+ throw t(
136
+ "src.components.video.components.lzc_overlay.use_cast.not_selected_device",
137
+ "没有选择设备",
138
+ )
139
+ }
140
+
141
+ function updatePosInfo(player: LzcPlayer, sessionId: number) {
142
+ if (_posInfoId) {
143
+ clearTimeout(_posInfoId)
144
+ }
145
+ sdk.rmp
146
+ .GetPositionInfo({ playerUuid: castDevice.value?.uuid })
147
+ .then((res) => {
148
+ if (sessionId !== _castSessionId) {
149
+ return
150
+ }
151
+ const duration = parseSecond(res.trackDuration)
152
+ if (duration > 0 && !player.scrubbing()) {
153
+ player.duration(duration)
154
+ }
155
+
156
+ const time = parseSecond(res.absTime)
157
+ if (time > 0 && !player.scrubbing()) {
158
+ player.currentTime(time)
159
+ player.trigger({ type: "timeupdate" })
160
+ }
161
+
162
+ if (castSubscribe.value) {
163
+ _posInfoId = setTimeout(() => updatePosInfo(player, sessionId), 1000)
164
+ }
165
+ })
166
+ }
167
+
168
+ export async function doPlayAction(player: LzcPlayer, mediaFile: string) {
169
+ const urls = mediaFile.split("?")
170
+ if (urls.length == 2) {
171
+ // lzcinit dlna 不支持query参数
172
+ if (urls[0].includes("/_lzc/media/hls")) {
173
+ urls[0] = decodeURIComponent(urls[0])
174
+ urls[0] = urls[0].substring(urls[0].indexOf("/_lzc/media/hls"))
175
+ }
176
+ mediaFile = urls[0].startsWith("/")
177
+ ? urls[0]
178
+ .replace(/_lzc\/media\/hls/, "")
179
+ .replace("//", "/")
180
+ .replace("/master.m3u8", "")
181
+ : encodeURI(urls[0] + "?" + urls[1])
182
+ } else {
183
+ if (mediaFile.includes("/_lzc/media/hls")) {
184
+ mediaFile = decodeURIComponent(mediaFile)
185
+ mediaFile = mediaFile.substring(mediaFile.indexOf("/_lzc/media/hls"))
186
+ }
187
+ mediaFile = mediaFile.startsWith("/")
188
+ ? mediaFile
189
+ .replace(/_lzc\/media\/hls/, "")
190
+ .replace("//", "/")
191
+ .replace("/master.m3u8", "")
192
+ : encodeURI(mediaFile)
193
+ }
194
+ castLoading.value = true
195
+ try {
196
+ await doAction({
197
+ mediaFile: mediaFile,
198
+ action: DoActionRequest_Action.Play,
199
+ })
200
+ subscribe(player)
201
+ } finally {
202
+ castLoading.value = false
203
+ }
204
+ }
205
+
206
+ export async function doPauseAction() {
207
+ return doAction({
208
+ action: DoActionRequest_Action.Pause,
209
+ })
210
+ }
211
+
212
+ export async function doContinueAction() {
213
+ return doAction({
214
+ action: DoActionRequest_Action.Continue,
215
+ })
216
+ }
217
+
218
+ export async function doStopAction() {
219
+ await doAction({
220
+ action: DoActionRequest_Action.Stop,
221
+ })
222
+ return cleanupCastSession({ clearDevice: true })
223
+ }
224
+
225
+ export async function doSeekAction(time: number) {
226
+ return doAction({
227
+ action: DoActionRequest_Action.Seek,
228
+ seekTarget: formatTime(time),
229
+ })
230
+ }
231
+
232
+ export async function doPlayNextAction(player: LzcPlayer, mediaFile: string) {
233
+ detachCastSession()
234
+ return doPlayAction(player, mediaFile)
235
+ }
@@ -0,0 +1,41 @@
1
+ import throttle from "lodash.throttle"
2
+ import type { LzcPlayer } from "@/components/Video/player"
3
+ import { ref, onMounted, onUnmounted } from "vue"
4
+
5
+ export const useVideoName = (player: LzcPlayer) => {
6
+ const videoName = ref("")
7
+ const updateVideoName = () => {
8
+ videoName.value = player.currentVideoName?.() || ""
9
+ }
10
+ const throttledUpdate = throttle(updateVideoName, 3000, { leading: true })
11
+
12
+ onMounted(() => {
13
+ updateVideoName()
14
+ player.on("openVideo", updateVideoName)
15
+ player.on("loadedmetadata", updateVideoName)
16
+ player.on("canplay", updateVideoName)
17
+ player.on("play", updateVideoName)
18
+ player.on("timeupdate", throttledUpdate)
19
+ })
20
+
21
+ onUnmounted(() => {
22
+ player.off("openVideo", updateVideoName)
23
+ player.off("loadedmetadata", updateVideoName)
24
+ player.off("canplay", updateVideoName)
25
+ player.off("play", updateVideoName)
26
+ player.off("timeupdate", throttledUpdate)
27
+ throttledUpdate.cancel()
28
+ })
29
+
30
+ return {
31
+ videoName,
32
+ }
33
+ }
34
+
35
+ export function parseSecond(t: string | number): number {
36
+ if (typeof t === "number") {
37
+ return t
38
+ }
39
+ const [hour, minute, second] = t.split(":").map((d) => parseInt(d))
40
+ return hour * 3600 + minute * 60 + second
41
+ }