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,53 @@
1
+ <script lang="ts" setup>
2
+ import { ref, onMounted, onUnmounted } from "vue"
3
+ import { NativePlayer } from "./NativePlayer"
4
+ import type { NativePlayerOptions } from "./NativePlayer"
5
+
6
+ const props = withDefaults(
7
+ defineProps<{
8
+ options?: NativePlayerOptions
9
+ poster?: string
10
+ }>(),
11
+ {},
12
+ )
13
+
14
+ const emit = defineEmits<{
15
+ (e: "ready", player: NativePlayer): void
16
+ (e: "destroy"): void
17
+ }>()
18
+
19
+ const videoRef = ref<HTMLVideoElement | null>(null)
20
+ let playerInstance: NativePlayer | null = null
21
+
22
+ function getPlayer(): NativePlayer | null {
23
+ return playerInstance
24
+ }
25
+
26
+ onMounted(() => {
27
+ const el = videoRef.value
28
+ if (!el) return
29
+ playerInstance = new NativePlayer(el, props.options ?? {})
30
+ emit("ready", playerInstance)
31
+ })
32
+
33
+ onUnmounted(() => {
34
+ if (playerInstance && !playerInstance.isDisposed()) {
35
+ playerInstance.dispose()
36
+ }
37
+ playerInstance = null
38
+ emit("destroy")
39
+ })
40
+
41
+ defineExpose({
42
+ getPlayer,
43
+ })
44
+ </script>
45
+
46
+ <template>
47
+ <video
48
+ ref="videoRef"
49
+ class="lzc-video-tech absolute inset-0 w-full h-full object-contain block bg-black"
50
+ playsinline
51
+ webkit-playsinline
52
+ />
53
+ </template>
@@ -0,0 +1,9 @@
1
+ export { EventEmitter } from "./EventEmitter"
2
+ export {
3
+ NativePlayer,
4
+ createStubQualityLevels,
5
+ type NativePlayerAPI,
6
+ type NativePlayerOptions,
7
+ type StubQualityLevels,
8
+ } from "./NativePlayer"
9
+ export { default as NativePlayerComponent } from "./NativePlayer.vue"
@@ -0,0 +1,183 @@
1
+ @tailwind components;
2
+ @tailwind utilities;
3
+
4
+ /* Scoped reset to keep Tailwind utilities working without global preflight. */
5
+ .lzc-video-player,
6
+ .lzc-video-player::backdrop {
7
+ box-sizing: border-box;
8
+ --tw-border-spacing-x: 0;
9
+ --tw-border-spacing-y: 0;
10
+ --tw-translate-x: 0;
11
+ --tw-translate-y: 0;
12
+ --tw-rotate: 0;
13
+ --tw-skew-x: 0;
14
+ --tw-skew-y: 0;
15
+ --tw-scale-x: 1;
16
+ --tw-scale-y: 1;
17
+ --tw-pan-x: ;
18
+ --tw-pan-y: ;
19
+ --tw-pinch-zoom: ;
20
+ --tw-scroll-snap-strictness: proximity;
21
+ --tw-gradient-from-position: ;
22
+ --tw-gradient-via-position: ;
23
+ --tw-gradient-to-position: ;
24
+ --tw-ordinal: ;
25
+ --tw-slashed-zero: ;
26
+ --tw-numeric-figure: ;
27
+ --tw-numeric-spacing: ;
28
+ --tw-numeric-fraction: ;
29
+ --tw-ring-inset: ;
30
+ --tw-ring-offset-width: 0px;
31
+ --tw-ring-offset-color: #fff;
32
+ --tw-ring-color: rgb(59 130 246 / 0.5);
33
+ --tw-ring-offset-shadow: 0 0 #0000;
34
+ --tw-ring-shadow: 0 0 #0000;
35
+ --tw-shadow: 0 0 #0000;
36
+ --tw-shadow-colored: 0 0 #0000;
37
+ --tw-blur: ;
38
+ --tw-brightness: ;
39
+ --tw-contrast: ;
40
+ --tw-grayscale: ;
41
+ --tw-hue-rotate: ;
42
+ --tw-invert: ;
43
+ --tw-saturate: ;
44
+ --tw-sepia: ;
45
+ --tw-drop-shadow: ;
46
+ --tw-backdrop-blur: ;
47
+ --tw-backdrop-brightness: ;
48
+ --tw-backdrop-contrast: ;
49
+ --tw-backdrop-grayscale: ;
50
+ --tw-backdrop-hue-rotate: ;
51
+ --tw-backdrop-invert: ;
52
+ --tw-backdrop-opacity: ;
53
+ --tw-backdrop-saturate: ;
54
+ --tw-backdrop-sepia: ;
55
+ --tw-contain-size: ;
56
+ --tw-contain-layout: ;
57
+ --tw-contain-paint: ;
58
+ --tw-contain-style: ;
59
+ }
60
+
61
+ .lzc-video-player *,
62
+ .lzc-video-player *::before,
63
+ .lzc-video-player *::after {
64
+ box-sizing: inherit;
65
+ }
66
+
67
+ .lzc-video-player {
68
+ position: relative;
69
+ isolation: isolate;
70
+ --lzc-player-safe-area-left: 0px;
71
+ --lzc-player-safe-area-right: 0px;
72
+ }
73
+
74
+ .lzc-video-player .lzc-video-stage {
75
+ isolation: isolate;
76
+ }
77
+
78
+ .lzc-video-player .lzc-video-tech {
79
+ z-index: 0;
80
+ -webkit-backface-visibility: hidden;
81
+ backface-visibility: hidden;
82
+ }
83
+
84
+ .lzc-video-player .lzc-player-foreground,
85
+ .lzc-video-player .lzc-player-spinner,
86
+ .lzc-video-player .lzc-player-modal,
87
+ .lzc-video-player .lzc-player-controls {
88
+ -webkit-backface-visibility: hidden;
89
+ backface-visibility: hidden;
90
+ }
91
+
92
+ .lzc-video-player .lzc-player-foreground {
93
+ z-index: 20;
94
+ }
95
+
96
+ .lzc-video-player .lzc-player-spinner {
97
+ z-index: 30;
98
+ }
99
+
100
+ .lzc-video-player .lzc-player-modal {
101
+ z-index: 35;
102
+ }
103
+
104
+ .lzc-video-player .lzc-player-controls {
105
+ z-index: 40;
106
+ }
107
+
108
+ .lzc-video-player.vjs-ios-fake-fullscreen {
109
+ position: fixed !important;
110
+ top: 50% !important;
111
+ left: 50% !important;
112
+ width: var(--lzc-ios-fs-width, 100vw) !important;
113
+ height: var(--lzc-ios-fs-height, 100vh) !important;
114
+ --lzc-safe-area-inset-top: 0px;
115
+ --lzc-safe-area-inset-bottom: 0px;
116
+ --lzc-safe-area-inset-left: var(
117
+ --lzc-safe-area-left,
118
+ env(safe-area-inset-left, 0px)
119
+ );
120
+ --lzc-safe-area-inset-right: var(
121
+ --lzc-safe-area-right,
122
+ env(safe-area-inset-right, 0px)
123
+ );
124
+ --lzc-player-safe-area-left: var(--lzc-safe-area-inset-left);
125
+ --lzc-player-safe-area-right: var(--lzc-safe-area-inset-right);
126
+ transform: translate(-50%, -50%) rotate(var(--lzc-ios-fs-rotate, 0deg));
127
+ transform-origin: center center;
128
+ transition: transform 260ms cubic-bezier(0.22, 0.61, 0.36, 1);
129
+ will-change: transform;
130
+ z-index: 9999 !important;
131
+ background: #000;
132
+ }
133
+
134
+ .lzc-video-player.vjs-ios-fake-fullscreen-portrait {
135
+ --lzc-safe-area-inset-left: var(
136
+ --lzc-safe-area-top,
137
+ env(safe-area-inset-top, 0px)
138
+ );
139
+ --lzc-safe-area-inset-right: var(
140
+ --lzc-safe-area-bottom,
141
+ env(safe-area-inset-bottom, 0px)
142
+ );
143
+ --lzc-player-safe-area-left: var(--lzc-safe-area-inset-left);
144
+ --lzc-player-safe-area-right: var(--lzc-safe-area-inset-right);
145
+ }
146
+
147
+ .lzc-video-player.vjs-ios-fake-fullscreen-exiting {
148
+ --lzc-safe-area-inset-top: var(
149
+ --lzc-safe-area-top,
150
+ env(safe-area-inset-top, 0px)
151
+ );
152
+ --lzc-safe-area-inset-bottom: var(
153
+ --lzc-safe-area-bottom,
154
+ env(safe-area-inset-bottom, 0px)
155
+ );
156
+ --lzc-player-safe-area-left: 0px;
157
+ --lzc-player-safe-area-right: 0px;
158
+ }
159
+
160
+ .lzc-video-player.vjs-ios-fake-fullscreen,
161
+ .lzc-video-player.vjs-ios-fake-fullscreen .lzc-video-stage {
162
+ -webkit-transform-style: preserve-3d;
163
+ transform-style: preserve-3d;
164
+ }
165
+
166
+ .lzc-video-player.vjs-ios-fake-fullscreen .lzc-video-tech {
167
+ -webkit-transform: translate3d(0, 0, 0);
168
+ transform: translate3d(0, 0, 0);
169
+ }
170
+
171
+ .lzc-video-player.vjs-ios-fake-fullscreen .lzc-player-foreground,
172
+ .lzc-video-player.vjs-ios-fake-fullscreen .lzc-player-spinner,
173
+ .lzc-video-player.vjs-ios-fake-fullscreen .lzc-player-modal,
174
+ .lzc-video-player.vjs-ios-fake-fullscreen .lzc-player-controls {
175
+ -webkit-transform: translate3d(0, 0, 1px);
176
+ transform: translate3d(0, 0, 1px);
177
+ }
178
+
179
+ @media (prefers-reduced-motion: reduce) {
180
+ .lzc-video-player.vjs-ios-fake-fullscreen {
181
+ transition: none;
182
+ }
183
+ }
@@ -0,0 +1,5 @@
1
+ import type { InjectionKey, Ref } from "vue"
2
+ import type { NativePlayerAPI } from "./NativePlayer"
3
+
4
+ export const nativePlayerKey: InjectionKey<Ref<NativePlayerAPI | null>> =
5
+ Symbol("nativePlayer")
@@ -0,0 +1,50 @@
1
+ import type { VideoInfo } from "@/model"
2
+ import type { LzcPlayer } from "../player"
3
+ import {
4
+ detachCastSession,
5
+ doPlayNextAction,
6
+ doSeekAction,
7
+ } from "../components/LzcOverlay/useCast"
8
+
9
+ export function useNativeCastMiddleware(player: LzcPlayer) {
10
+ player.on("startCasting", (e: any) => {
11
+ if (e?.preview === true) {
12
+ return
13
+ }
14
+ player.src({
15
+ src: player.currentSrc(),
16
+ type: "video/lzc-cast",
17
+ })
18
+ })
19
+
20
+ player.on("closeCasting", () => {
21
+ if (!player.isCastMode()) {
22
+ detachCastSession()
23
+ return
24
+ }
25
+ const time = player.currentTime() as number
26
+ const src = player.currentSrc()
27
+ detachCastSession()
28
+ player.src({ src })
29
+ if (Number.isFinite(time)) {
30
+ player.currentTime(time)
31
+ }
32
+ })
33
+
34
+ player.on("lzcSeekBarUp", () => {
35
+ if (player.isCastMode()) {
36
+ doSeekAction((player.currentTime() as number) || 0)
37
+ }
38
+ })
39
+
40
+ player.on("openVideo", async (e: any) => {
41
+ if (!player.isCastMode()) return
42
+ const info = e.info as VideoInfo
43
+ console.debug("cast openVideo: ", info)
44
+ await doPlayNextAction(player, info.sourceUrl)
45
+ player.src({
46
+ src: info.sourceUrl,
47
+ type: "video/lzc-cast",
48
+ })
49
+ })
50
+ }
@@ -0,0 +1,3 @@
1
+ // useNativePlayer - composable version of NativePlayer
2
+ export type { NativePlayerOptions, StubQualityLevels, NativePlayer } from "./NativePlayer"
3
+ export { createStubQualityLevels } from "./NativePlayer"
@@ -0,0 +1,44 @@
1
+ import { ref, type Ref } from "vue"
2
+ import type { NativePlayerAPI } from "./NativePlayer"
3
+ import { isMobile } from "@/use/useUtils"
4
+
5
+ export function useNativePlayerFullscreen(
6
+ player: Ref<NativePlayerAPI | null>,
7
+ options: { showTopBar?: boolean },
8
+ ) {
9
+ const showTopBar = ref(true)
10
+ let boundPlayer: NativePlayerAPI | null = null
11
+
12
+ function onFullscreenChange() {
13
+ const p = player.value
14
+ if (!p) return
15
+ if (p.isFullscreen()) {
16
+ showTopBar.value = options.showTopBar !== false || isMobile()
17
+ } else {
18
+ showTopBar.value = options.showTopBar !== false
19
+ }
20
+ }
21
+
22
+ function bind(p: NativePlayerAPI) {
23
+ if (boundPlayer === p) return
24
+ if (boundPlayer) {
25
+ boundPlayer.off("fullscreenchange", onFullscreenChange)
26
+ }
27
+ boundPlayer = p
28
+ p.on("fullscreenchange", onFullscreenChange)
29
+ onFullscreenChange()
30
+ }
31
+
32
+ function unbindIfNeeded() {
33
+ if (!boundPlayer) return
34
+ boundPlayer.off("fullscreenchange", onFullscreenChange)
35
+ boundPlayer = null
36
+ }
37
+
38
+ return {
39
+ showTopBar,
40
+ onFullscreenChange,
41
+ bind,
42
+ unbindIfNeeded,
43
+ }
44
+ }
@@ -0,0 +1,69 @@
1
+ import type { Ref } from "vue"
2
+ import type { NativePlayerAPI } from "./NativePlayer"
3
+ import debounce from "lodash.debounce"
4
+ import { makeVideoFrame } from "../videoFrame"
5
+ import type { useHistoryInfo } from "@/stores/playlist"
6
+
7
+ export function useNativePlayerHistory(
8
+ player: Ref<NativePlayerAPI | null>,
9
+ store: ReturnType<typeof useHistoryInfo>,
10
+ ) {
11
+ function updateHistory(updateCover: boolean) {
12
+ const p = player.value
13
+ if (!p || p.isDisposed()) return
14
+ const currentInfo = p.currentVideoInfo()
15
+ if (!currentInfo) return
16
+ const info = {
17
+ ...currentInfo,
18
+ duration: p.duration() ?? currentInfo.duration,
19
+ currentTime: p.currentTime() ?? currentInfo.currentTime,
20
+ name: p.currentVideoName() ?? currentInfo.name,
21
+ }
22
+ if (p.ended()) {
23
+ info.currentTime = 0
24
+ }
25
+ const blobFn = updateCover ? () => makeVideoFrame(p as any) : null
26
+ store.updateOrCreateHistory(info as any, blobFn)
27
+ }
28
+
29
+ const debouncedUpdateHistory = debounce(() => updateHistory(false), 5000, {
30
+ maxWait: 15000,
31
+ })
32
+
33
+ let unbind: (() => void) | null = null
34
+
35
+ function bind(p: NativePlayerAPI) {
36
+ unbind?.()
37
+ const onBack = () => updateHistory(false)
38
+ const onPause = () => updateHistory(true)
39
+ const onPlay = () => debouncedUpdateHistory()
40
+ const onSubtitleChange = () => debouncedUpdateHistory()
41
+ p.on("back", onBack)
42
+ p.on("seeked", debouncedUpdateHistory)
43
+ p.on("pause", onPause)
44
+ p.on("play", onPlay)
45
+ p.on("timeupdate", debouncedUpdateHistory)
46
+ p.on("ended", onBack)
47
+ p.on("subtitlechange", onSubtitleChange)
48
+ unbind = () => {
49
+ p.off("back", onBack)
50
+ p.off("seeked", debouncedUpdateHistory)
51
+ p.off("pause", onPause)
52
+ p.off("play", onPlay)
53
+ p.off("timeupdate", debouncedUpdateHistory)
54
+ p.off("ended", onBack)
55
+ p.off("subtitlechange", onSubtitleChange)
56
+ debouncedUpdateHistory.cancel()
57
+ unbind = null
58
+ }
59
+ }
60
+
61
+ return {
62
+ updateHistory,
63
+ debouncedUpdateHistory,
64
+ bind,
65
+ unbindIfNeeded: () => {
66
+ unbind?.()
67
+ },
68
+ }
69
+ }
@@ -0,0 +1,68 @@
1
+ import { ref } from "vue"
2
+
3
+ export function useNativePlayerModal() {
4
+ const modalVisible = ref(false)
5
+ const modalPage = ref("")
6
+ const modalPosition = ref({ right: 0, bottom: 0 })
7
+
8
+ function getTargetLayoutRect(target: HTMLElement, containerEl: HTMLElement) {
9
+ let left = 0
10
+ let top = 0
11
+ let node: HTMLElement | null = target
12
+ while (node && node !== containerEl) {
13
+ left += node.offsetLeft
14
+ top += node.offsetTop
15
+ node = node.offsetParent as HTMLElement | null
16
+ }
17
+ if (node === containerEl) {
18
+ return {
19
+ left,
20
+ top,
21
+ width: target.offsetWidth || 0,
22
+ height: target.offsetHeight || 0,
23
+ }
24
+ }
25
+ return null
26
+ }
27
+
28
+ function setMenuPosition(ev: Event, containerEl: HTMLElement | null) {
29
+ const target =
30
+ (ev.target as HTMLElement)?.closest?.("button") ||
31
+ (ev.target as HTMLElement)
32
+ if (!target || !containerEl) return
33
+ const layoutRect = getTargetLayoutRect(target, containerEl)
34
+ const fallbackTargetRect = target.getBoundingClientRect()
35
+ const fallbackContainerRect = containerEl.getBoundingClientRect()
36
+ const left = layoutRect
37
+ ? layoutRect.left
38
+ : fallbackTargetRect.left - fallbackContainerRect.left
39
+ const top = layoutRect
40
+ ? layoutRect.top
41
+ : fallbackTargetRect.top - fallbackContainerRect.top
42
+ const width = layoutRect ? layoutRect.width : fallbackTargetRect.width
43
+ modalPosition.value = {
44
+ right: containerEl.clientWidth - (left + width / 2),
45
+ bottom: containerEl.clientHeight - top,
46
+ }
47
+ }
48
+
49
+ function openModal(page: string, ev: Event, containerEl: HTMLElement | null) {
50
+ setMenuPosition(ev, containerEl)
51
+ modalPage.value = page
52
+ modalVisible.value = true
53
+ }
54
+
55
+ function closeModal() {
56
+ modalVisible.value = false
57
+ modalPage.value = ""
58
+ }
59
+
60
+ return {
61
+ modalVisible,
62
+ modalPage,
63
+ modalPosition,
64
+ openModal,
65
+ closeModal,
66
+ setMenuPosition,
67
+ }
68
+ }
@@ -0,0 +1,67 @@
1
+ import type { Ref } from "vue"
2
+ import type { NativePlayerAPI } from "./NativePlayer"
3
+ import type { useHistoryInfo } from "@/stores/playlist"
4
+ import { isSourceEqual } from "@/use/useUtils"
5
+ import type { VideoInfo } from "@/model"
6
+
7
+ export function useNativePlayerPlaylist(
8
+ player: Ref<NativePlayerAPI | null>,
9
+ store: ReturnType<typeof useHistoryInfo>,
10
+ options: {
11
+ playPrev?: () => void
12
+ playNext?: () => void
13
+ },
14
+ ) {
15
+ function findCurrentIndex(p: NativePlayerAPI): number {
16
+ const currentInfo = p.currentVideoInfo()
17
+ if (currentInfo?.sourceUrl) {
18
+ const idxByInfo = store.infos.findIndex((info: VideoInfo) => {
19
+ try {
20
+ return isSourceEqual(info.sourceUrl, currentInfo)
21
+ } catch {
22
+ if (info.sourceUrl === currentInfo.sourceUrl) return true
23
+ return !!info.path && !!currentInfo.path && info.path === currentInfo.path
24
+ }
25
+ })
26
+ if (idxByInfo >= 0) return idxByInfo
27
+ }
28
+
29
+ const src = p.currentSrc()
30
+ return store.infos.findIndex((info: VideoInfo) => {
31
+ try {
32
+ return isSourceEqual(src, info)
33
+ } catch {
34
+ return info.sourceUrl === src
35
+ }
36
+ })
37
+ }
38
+
39
+ function bind(p: NativePlayerAPI) {
40
+ p.playPrev = () => {
41
+ if (options.playPrev) {
42
+ options.playPrev()
43
+ return
44
+ }
45
+ store.ready.then(() => {
46
+ const idx = findCurrentIndex(p)
47
+ if (idx > 0) p.trigger({ type: "openVideo", info: store.infos[idx - 1] })
48
+ })
49
+ }
50
+ p.playNext = () => {
51
+ if (options.playNext) {
52
+ options.playNext()
53
+ return
54
+ }
55
+ store.ready.then(() => {
56
+ const idx = findCurrentIndex(p)
57
+ if (idx >= 0 && idx < store.infos.length - 1) {
58
+ p.trigger({ type: "openVideo", info: store.infos[idx + 1] })
59
+ }
60
+ })
61
+ }
62
+ }
63
+
64
+ return {
65
+ bind,
66
+ }
67
+ }