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.
- package/.dockerignore +1 -0
- package/.eslintrc.cjs +18 -0
- package/.prettierrc.json +5 -0
- package/AGENTS.md +31 -0
- package/README.md +38 -0
- package/build.sh +10 -0
- package/demo/.vscode/extensions.json +3 -0
- package/demo/README.md +40 -0
- package/demo/env.d.ts +1 -0
- package/demo/index.html +13 -0
- package/demo/package-lock.json +2037 -0
- package/demo/package.json +25 -0
- package/demo/public/favicon.ico +0 -0
- package/demo/src/App.vue +25 -0
- package/demo/src/assets/base.css +70 -0
- package/demo/src/assets/logo.svg +1 -0
- package/demo/src/assets/main.css +33 -0
- package/demo/src/main.ts +8 -0
- package/demo/tsconfig.config.json +8 -0
- package/demo/tsconfig.json +16 -0
- package/demo/vite.config.ts +14 -0
- package/docs/progress-bar-style-analysis.md +87 -0
- package/env.d.ts +1 -0
- package/error_pages/502.html.tpl +13 -0
- package/i18next-parser.config.mjs +147 -0
- package/index.html +54 -0
- package/lazycat.png +0 -0
- package/lib/README.md +48 -0
- package/lib/package.json +22 -0
- package/lzc-build.local.yml +65 -0
- package/lzc-build.yml +65 -0
- package/lzc-manifest.yml +53 -0
- package/makefile +15 -0
- package/package.json +69 -0
- package/postcss.config.js +6 -0
- package/public/512x512.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/languages/en/translation.json +125 -0
- package/public/languages/zh/translation.json +125 -0
- package/public/libass-wasm/4.1.0/default.woff2 +0 -0
- package/public/libass-wasm/4.1.0/subtitles-octopus-worker-legacy.js +40 -0
- package/public/libass-wasm/4.1.0/subtitles-octopus-worker.js +1 -0
- package/public/libass-wasm/4.1.0/subtitles-octopus-worker.wasm +0 -0
- package/public/libass-wasm/4.1.0/subtitles-octopus.js +1680 -0
- package/public/square-128x128.png +0 -0
- package/public/square-256x256.png +0 -0
- package/public/square-512x512.png +0 -0
- package/src/App.vue +18 -0
- package/src/assets/base.scss +104 -0
- package/src/assets/cloud.png +0 -0
- package/src/assets/logo.svg +1 -0
- package/src/components/Dialog/index.vue +96 -0
- package/src/components/MultipleEdit/choose.vue +39 -0
- package/src/components/PlayList/index.vue +521 -0
- package/src/components/Spectrum/index.vue +58 -0
- package/src/components/Video/NativeVideoPlayer.vue +748 -0
- package/src/components/Video/README.md +3 -0
- package/src/components/Video/clientPlayer.ts +348 -0
- package/src/components/Video/components/LzcModal/components/simpleList.vue +57 -0
- package/src/components/Video/components/LzcModal/list.vue +52 -0
- package/src/components/Video/components/LzcModal/playrate.vue +45 -0
- package/src/components/Video/components/LzcModal/resolution.vue +117 -0
- package/src/components/Video/components/LzcModal/subtitle.vue +499 -0
- package/src/components/Video/components/LzcModal/useModal.ts +18 -0
- package/src/components/Video/components/LzcOverlay/SubtitleLayer.vue +321 -0
- package/src/components/Video/components/LzcOverlay/cast.vue +253 -0
- package/src/components/Video/components/LzcOverlay/casting.vue +205 -0
- package/src/components/Video/components/LzcOverlay/error.vue +103 -0
- package/src/components/Video/components/LzcOverlay/helper.ts +81 -0
- package/src/components/Video/components/LzcOverlay/index.vue +99 -0
- package/src/components/Video/components/LzcOverlay/playing.vue +496 -0
- package/src/components/Video/components/LzcOverlay/playingButtons.vue +122 -0
- package/src/components/Video/components/LzcOverlay/playingLayout.vue +287 -0
- package/src/components/Video/components/LzcOverlay/useCast.ts +235 -0
- package/src/components/Video/components/LzcOverlay/useCommon.ts +41 -0
- package/src/components/Video/components/LzcOverlay/useOctopusRenderer.ts +230 -0
- package/src/components/Video/components/LzcOverlay/useSubtitleRenderEngine.ts +79 -0
- package/src/components/Video/components/LzcOverlay/useSubtitleTrack.ts +139 -0
- package/src/components/Video/components/useLzcCommon.ts +16 -0
- package/src/components/Video/directPlay.ts +345 -0
- package/src/components/Video/getSubtitleInfo.ts +42 -0
- package/src/components/Video/native/EventEmitter.ts +62 -0
- package/src/components/Video/native/NativeControls.vue +510 -0
- package/src/components/Video/native/NativeModal.vue +133 -0
- package/src/components/Video/native/NativePlayer.ts +913 -0
- package/src/components/Video/native/NativePlayer.vue +53 -0
- package/src/components/Video/native/index.ts +9 -0
- package/src/components/Video/native/native-player.css +183 -0
- package/src/components/Video/native/playerKey.ts +5 -0
- package/src/components/Video/native/useNativeCastMiddleware.ts +50 -0
- package/src/components/Video/native/useNativePlayer.ts +3 -0
- package/src/components/Video/native/useNativePlayerFullscreen.ts +44 -0
- package/src/components/Video/native/useNativePlayerHistory.ts +69 -0
- package/src/components/Video/native/useNativePlayerModal.ts +68 -0
- package/src/components/Video/native/useNativePlayerPlaylist.ts +67 -0
- package/src/components/Video/native/useNativePlayerState.ts +225 -0
- package/src/components/Video/player.ts +99 -0
- package/src/components/Video/theme/index.scss +291 -0
- package/src/components/Video/theme/videojs.css +1797 -0
- package/src/components/Video/useSource.ts +1431 -0
- package/src/components/Video/useSubtitlePreference.ts +66 -0
- package/src/components/Video/useWebview.ts +79 -0
- package/src/components/Video/videoFrame.ts +58 -0
- package/src/env.d.ts +3 -0
- package/src/i18n/README.md +392 -0
- package/src/i18n/index.ts +49 -0
- package/src/icons/Video_Player.svg +69 -0
- package/src/icons/box.svg +15 -0
- package/src/icons/client.svg +17 -0
- package/src/icons/logo.svg +28 -0
- package/src/icons//344/270/212/344/270/200/344/270/252.svg +6 -0
- package/src/icons//344/270/213/344/270/200/344/270/252.svg +4 -0
- package/src/icons//344/272/256/345/272/246.svg +13 -0
- package/src/icons//345/200/215/351/200/237.svg +14 -0
- package/src/icons//345/205/250/345/261/217.svg +16 -0
- package/src/icons//345/205/250/351/200/211_/345/267/262/351/200/211/344/270/255.svg +16 -0
- package/src/icons//345/205/250/351/200/211_/346/234/252/351/200/211/344/270/255.svg +15 -0
- package/src/icons//345/205/263/351/227/255/345/244/232/351/200/211.svg +14 -0
- package/src/icons//345/205/263/351/227/255/346/212/225/345/261/217.svg +11 -0
- package/src/icons//345/233/236/346/224/266/347/253/231.svg +15 -0
- package/src/icons//345/244/261/346/225/210.svg +17 -0
- package/src/icons//346/207/222/347/214/253/346/222/255/346/224/276/345/231/250-icon.png +0 -0
- package/src/icons//346/207/222/347/214/253/346/222/255/346/224/276/345/231/250.png +0 -0
- package/src/icons//346/212/225/345/261/217.svg +11 -0
- package/src/icons//346/212/225/351/200/201/344/270/255.jpg +0 -0
- package/src/icons//346/212/225/351/200/201/344/270/255.svg +21 -0
- package/src/icons//346/222/255/346/224/276.svg +3 -0
- package/src/icons//346/232/202/345/201/234.svg +4 -0
- package/src/icons//346/232/202/346/227/240.svg +21 -0
- package/src/icons//346/233/264/345/244/232/346/223/215/344/275/234.svg +11 -0
- package/src/icons//347/224/265/350/247/206.svg +18 -0
- package/src/icons//347/247/273/345/212/250/347/253/257_/350/203/214/346/231/257.webp +0 -0
- package/src/icons//350/203/214/346/231/257.png +0 -0
- package/src/icons//350/277/224/345/233/236.svg +13 -0
- package/src/icons//350/277/233/345/205/245/345/205/250/345/261/217.svg +13 -0
- package/src/icons//351/200/200/345/207/272/345/205/250/345/261/217.svg +15 -0
- package/src/icons//351/200/211/346/213/251.svg +15 -0
- package/src/icons//351/237/263/351/207/217.svg +13 -0
- package/src/index.d.ts +9 -0
- package/src/lzc-video-player.scss +7 -0
- package/src/lzc-video-player.ts +6 -0
- package/src/main.ts +62 -0
- package/src/model.ts +77 -0
- package/src/quasar-variables.sass +10 -0
- package/src/router/index.ts +74 -0
- package/src/stores/pinia.ts +3 -0
- package/src/stores/playlist.ts +146 -0
- package/src/use/useKeyBind.ts +61 -0
- package/src/use/useMultipleEdit.ts +60 -0
- package/src/use/useSdk.ts +5 -0
- package/src/use/useSubtitle.ts +39 -0
- package/src/use/useUtils.ts +22 -0
- package/src/use/useVideoFrame.ts +60 -0
- package/src/views/Home.ts +99 -0
- package/src/views/mobile/Home.vue +246 -0
- package/src/views/mobile/Player.vue +141 -0
- package/tailwind.config.js +15 -0
- package/tsconfig.config.json +8 -0
- package/tsconfig.json +20 -0
- package/vite.config.lib.ts +88 -0
- package/vite.config.ts +122 -0
- 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,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,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
|
+
}
|