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,99 @@
|
|
|
1
|
+
import { nextTick, ref, type Ref } from "vue"
|
|
2
|
+
import { useRouter } from "vue-router"
|
|
3
|
+
import type { FileStat } from "@lazycatcloud/webdav/web"
|
|
4
|
+
import type { VideoInfo } from "@/model"
|
|
5
|
+
import { createClient } from "webdav/web"
|
|
6
|
+
import { useHistoryInfo } from "@/stores/playlist"
|
|
7
|
+
|
|
8
|
+
const webdavClient = createClient("/_lzc/files/home/")
|
|
9
|
+
|
|
10
|
+
function dataTransfer<T>(raw: any) {
|
|
11
|
+
if (!raw) return
|
|
12
|
+
const output = raw.detail[0]
|
|
13
|
+
return JSON.parse(output) as T
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function (ctx: Ref<any>) {
|
|
17
|
+
const router = useRouter()
|
|
18
|
+
const store = useHistoryInfo()
|
|
19
|
+
async function invoke(method: string, ...args: string[]) {
|
|
20
|
+
for (let i = 0; i < 5; i++) {
|
|
21
|
+
await nextTick()
|
|
22
|
+
const target = ctx.value
|
|
23
|
+
const exposed = target?._instance?.exposed ?? target
|
|
24
|
+
if (exposed && typeof exposed[method] === "function") {
|
|
25
|
+
exposed[method](...args)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
await new Promise((r) => setTimeout(r, 50))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const openFilePicker = () => {
|
|
33
|
+
invoke("init")
|
|
34
|
+
invoke("open")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const closeFilePicker = () => {
|
|
38
|
+
invoke("close")
|
|
39
|
+
}
|
|
40
|
+
const getSourceUrl = (file: any, boxname: string) => {
|
|
41
|
+
const videopath = encodeURIComponent(file.filename)
|
|
42
|
+
const owner = file?.owner || ""
|
|
43
|
+
const _owner = owner ? `?X_LZCAPI_UID=${owner}` : ""
|
|
44
|
+
return `https://video.${boxname}.heiyu.space/_lzc/media/hls${videopath}/quality-720.m3u8${_owner}`
|
|
45
|
+
}
|
|
46
|
+
// 从 filepicker 中选择的文件一定为网盘资源
|
|
47
|
+
const submit = async (e: CustomEvent, boxname: string) => {
|
|
48
|
+
const files = dataTransfer<FileStat[]>(e) || []
|
|
49
|
+
const file = files[0]
|
|
50
|
+
if (!file) return
|
|
51
|
+
const sourceUrl = getSourceUrl(file, boxname)
|
|
52
|
+
const info: VideoInfo = {
|
|
53
|
+
_id: "",
|
|
54
|
+
sourceUrl,
|
|
55
|
+
name: file.basename,
|
|
56
|
+
duration: 0,
|
|
57
|
+
currentTime: 0,
|
|
58
|
+
invalid: false,
|
|
59
|
+
fromNetdisk: true,
|
|
60
|
+
path: file.filename,
|
|
61
|
+
updateTime: undefined,
|
|
62
|
+
subtitles: undefined,
|
|
63
|
+
}
|
|
64
|
+
store.setPendingPlayInfo(info)
|
|
65
|
+
await router.push({
|
|
66
|
+
name: "link",
|
|
67
|
+
query: {
|
|
68
|
+
url: sourceUrl,
|
|
69
|
+
name: file.basename,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
const showList = ref(false)
|
|
74
|
+
const isPlaying = (_: VideoInfo) => {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
const onOpen = (info: VideoInfo) => {
|
|
78
|
+
store.setPendingPlayInfo(info)
|
|
79
|
+
router.push({
|
|
80
|
+
name: "link",
|
|
81
|
+
query: {
|
|
82
|
+
url: info.sourceUrl,
|
|
83
|
+
name: info.name,
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
const onBack = () => {
|
|
88
|
+
showList.value = false
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
openFilePicker,
|
|
92
|
+
closeFilePicker,
|
|
93
|
+
submit,
|
|
94
|
+
showList,
|
|
95
|
+
isPlaying,
|
|
96
|
+
onOpen,
|
|
97
|
+
onBack,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref, onMounted } from "vue"
|
|
3
|
+
import sdk from "@/use/useSdk"
|
|
4
|
+
import Playlist from "@/components/PlayList/index.vue"
|
|
5
|
+
import useHome from "../Home"
|
|
6
|
+
import PCBackgroundSvg from "@/icons/背景.png?inline"
|
|
7
|
+
import BackgroundSvg from "@/icons/移动端_背景.webp?inline"
|
|
8
|
+
import VideoNameSvg from "@/icons/懒猫播放器.png?inline"
|
|
9
|
+
import VideoNameEnSvg from "@/icons/Video_Player.svg?inline"
|
|
10
|
+
import { t } from "@/i18n"
|
|
11
|
+
import i18next from "i18next"
|
|
12
|
+
import { useBackPress } from "@lazycatcloud/lzc-toolkit"
|
|
13
|
+
import type { VideoInfo } from "@/model"
|
|
14
|
+
|
|
15
|
+
const filePickerInstance = ref()
|
|
16
|
+
const showPicker = ref(false)
|
|
17
|
+
|
|
18
|
+
const open = () => {
|
|
19
|
+
showPicker.value = true
|
|
20
|
+
openFilePicker()
|
|
21
|
+
toggleIosButton(false)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function close() {
|
|
25
|
+
showPicker.value = false
|
|
26
|
+
toggleIosButton(true)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { openFilePicker, submit, showList, isPlaying, onOpen, onBack } =
|
|
30
|
+
useHome(filePickerInstance)
|
|
31
|
+
const submitFn = async (e: CustomEvent) => {
|
|
32
|
+
await submit(e, boxName.value)
|
|
33
|
+
showPicker.value = false
|
|
34
|
+
}
|
|
35
|
+
const VideoNameIcon = ref(VideoNameSvg)
|
|
36
|
+
const boxName = ref("")
|
|
37
|
+
|
|
38
|
+
const allowIosGestures = async (allows: boolean) => {
|
|
39
|
+
// @ts-expect-error
|
|
40
|
+
return await window?.webkit?.messageHandlers?.[
|
|
41
|
+
"_AllowsNavigationGestures"
|
|
42
|
+
]?.postMessage({ allows })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toggleIosButton(visible: boolean) {
|
|
46
|
+
// @ts-expect-error
|
|
47
|
+
const bridge = window?.webkit?.messageHandlers?.["SetCloseBtnShowStatus"]
|
|
48
|
+
if (bridge?.postMessage) {
|
|
49
|
+
bridge.postMessage({
|
|
50
|
+
params: [visible],
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// disable back gestures
|
|
56
|
+
allowIosGestures(false)
|
|
57
|
+
toggleIosButton(true)
|
|
58
|
+
|
|
59
|
+
function openVideo(info: VideoInfo) {
|
|
60
|
+
toggleIosButton(false)
|
|
61
|
+
onOpen(info)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
useBackPress(() => {
|
|
65
|
+
return true
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
onMounted(async () => {
|
|
69
|
+
if (i18next.language.startsWith("en")) {
|
|
70
|
+
VideoNameIcon.value = VideoNameEnSvg
|
|
71
|
+
} else {
|
|
72
|
+
VideoNameIcon.value = VideoNameSvg
|
|
73
|
+
}
|
|
74
|
+
const QueryInfo = await sdk.box.QueryInfo({})
|
|
75
|
+
boxName.value = QueryInfo.boxName
|
|
76
|
+
})
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<template>
|
|
80
|
+
<div class="content">
|
|
81
|
+
<div style="flex: 4"></div>
|
|
82
|
+
<lzc-file-picker
|
|
83
|
+
v-if="boxName && showPicker"
|
|
84
|
+
class="file-picker-safe-area"
|
|
85
|
+
type="file"
|
|
86
|
+
ref="filePickerInstance"
|
|
87
|
+
base-url="/_lzc/files/home"
|
|
88
|
+
accept="video/*, audio/x-mpegurl, application/vnd.apple.mpegurl, application/vnd.rn-realmedia, application/dvd"
|
|
89
|
+
:boxId="boxName"
|
|
90
|
+
:multiple="false"
|
|
91
|
+
:isModal="true"
|
|
92
|
+
:choiceFileOnly="true"
|
|
93
|
+
:title="t('src.views.mobile.home.title_select_file', '选择网盘文件')"
|
|
94
|
+
:confirm-button-title="
|
|
95
|
+
t('src.views.mobile.home.button_start_play', '开始播放')
|
|
96
|
+
"
|
|
97
|
+
@close="close"
|
|
98
|
+
@submit="submitFn"
|
|
99
|
+
></lzc-file-picker>
|
|
100
|
+
<div
|
|
101
|
+
class="playlist-modal"
|
|
102
|
+
v-if="showList"
|
|
103
|
+
@click.self.prevent="showList = false"
|
|
104
|
+
>
|
|
105
|
+
<Playlist
|
|
106
|
+
class="playlist"
|
|
107
|
+
:isPlaying="isPlaying"
|
|
108
|
+
@openVideo="openVideo"
|
|
109
|
+
@back="onBack"
|
|
110
|
+
></Playlist>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="buttons">
|
|
113
|
+
<div class="video-home-logo">
|
|
114
|
+
<q-icon name="svguse:#icon-logo.svg" size="80px"></q-icon>
|
|
115
|
+
<img class="video-text-icon" :src="VideoNameIcon" />
|
|
116
|
+
</div>
|
|
117
|
+
<div class="video-operation-btns">
|
|
118
|
+
<q-btn class="btn video-open-file" @click="open">
|
|
119
|
+
{{ t("src.views.mobile.home.button_open_file", "打开文件") }}
|
|
120
|
+
</q-btn>
|
|
121
|
+
<q-btn class="btn video-play-list" @click="showList = true">
|
|
122
|
+
{{ t("src.views.mobile.home.button_playlist", "播放列表") }}
|
|
123
|
+
</q-btn>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
<style lang="scss" scoped>
|
|
130
|
+
.content {
|
|
131
|
+
display: flex;
|
|
132
|
+
flex-direction: column;
|
|
133
|
+
background-image: v-bind('"url(" + BackgroundSvg + ")"');
|
|
134
|
+
background-repeat: no-repeat;
|
|
135
|
+
background-position: center;
|
|
136
|
+
background-size: cover;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@media screen and (min-width: 720px) {
|
|
140
|
+
.content {
|
|
141
|
+
background-image: v-bind('"url(" + PCBackgroundSvg + ")"');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.buttons {
|
|
146
|
+
flex: 6;
|
|
147
|
+
display: flex;
|
|
148
|
+
flex-direction: column;
|
|
149
|
+
align-items: center;
|
|
150
|
+
|
|
151
|
+
.video-text-icon {
|
|
152
|
+
width: 14.9rem;
|
|
153
|
+
height: 2.9rem;
|
|
154
|
+
margin-top: 3.8rem;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.video-home-logo {
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
align-items: center;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@media screen and (max-height: 500px) {
|
|
165
|
+
.video-operation-btns {
|
|
166
|
+
margin-top: 30px;
|
|
167
|
+
margin-block: 50px;
|
|
168
|
+
position: relative !important;
|
|
169
|
+
bottom: var(--lzc-safe-area-inset-bottom) !important;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.video-operation-btns {
|
|
174
|
+
position: fixed;
|
|
175
|
+
bottom: calc(10rem + var(--lzc-safe-area-inset-bottom));
|
|
176
|
+
|
|
177
|
+
.btn {
|
|
178
|
+
width: 15.6rem;
|
|
179
|
+
height: 4.8rem;
|
|
180
|
+
border-radius: 1rem;
|
|
181
|
+
font-size: 1.6rem;
|
|
182
|
+
font-weight: 500;
|
|
183
|
+
line-height: 2.2rem;
|
|
184
|
+
max-width: calc((100vw - 1.5rem - 2.4rem * 2) / 2);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.btn + .btn {
|
|
188
|
+
margin-left: 1.5rem;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.video-open-file {
|
|
192
|
+
color: #ffffff;
|
|
193
|
+
background: #5f86ff;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.video-play-list {
|
|
197
|
+
color: #5f86ff;
|
|
198
|
+
background: transparent;
|
|
199
|
+
border: 1px solid #5f86ff;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.file-picker-safe-area {
|
|
204
|
+
position: fixed;
|
|
205
|
+
inset: 0;
|
|
206
|
+
z-index: 9999;
|
|
207
|
+
box-sizing: border-box;
|
|
208
|
+
width: 100%;
|
|
209
|
+
height: 100%;
|
|
210
|
+
padding-top: var(--lzc-safe-area-inset-top);
|
|
211
|
+
padding-bottom: var(--lzc-safe-area-inset-bottom);
|
|
212
|
+
background: #ffffff;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.playlist-modal {
|
|
216
|
+
width: 100%;
|
|
217
|
+
height: 100%;
|
|
218
|
+
position: absolute;
|
|
219
|
+
top: 0;
|
|
220
|
+
z-index: 1;
|
|
221
|
+
display: flex;
|
|
222
|
+
background-color: rgba(0, 0, 0, 0.82);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@media screen and (min-width: 667px) {
|
|
226
|
+
.playlist-modal {
|
|
227
|
+
flex-direction: row-reverse;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.playlist {
|
|
231
|
+
width: 65%;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@media screen and (max-width: 666px) {
|
|
236
|
+
.playlist-modal {
|
|
237
|
+
flex-direction: column;
|
|
238
|
+
justify-content: flex-end;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.playlist {
|
|
242
|
+
height: 65%;
|
|
243
|
+
width: 100%;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
</style>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { onUnmounted, watch, withDefaults } from "vue"
|
|
3
|
+
import NativeVideoPlayer from "@/components/Video/NativeVideoPlayer.vue"
|
|
4
|
+
import type { NativePlayerAPI } from "@/components/Video/native/NativePlayer"
|
|
5
|
+
import { useRouter } from "vue-router"
|
|
6
|
+
import { useHistoryInfo } from "@/stores/playlist"
|
|
7
|
+
import type { VideoInfo } from "@/model"
|
|
8
|
+
|
|
9
|
+
const store = useHistoryInfo()
|
|
10
|
+
const props = withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
url: string
|
|
13
|
+
name?: string
|
|
14
|
+
path?: string
|
|
15
|
+
fromNetdisk?: string
|
|
16
|
+
isUrl?: boolean
|
|
17
|
+
}>(),
|
|
18
|
+
{
|
|
19
|
+
isUrl: false,
|
|
20
|
+
},
|
|
21
|
+
)
|
|
22
|
+
const router = useRouter()
|
|
23
|
+
const back = () => router.replace({ name: "home" })
|
|
24
|
+
let boundPlayer: NativePlayerAPI | null = null
|
|
25
|
+
let unwatchUrl: (() => void) | null = null
|
|
26
|
+
let openVideoHandler: ((e?: any) => void) | null = null
|
|
27
|
+
|
|
28
|
+
const trigger = async (player: NativePlayerAPI) => {
|
|
29
|
+
let info: VideoInfo | undefined
|
|
30
|
+
const pendingInfo = store.pendingPlayInfo
|
|
31
|
+
const pendingMatchesRoute = !!pendingInfo?.sourceUrl
|
|
32
|
+
? props.path && pendingInfo.path
|
|
33
|
+
? pendingInfo.path === props.path
|
|
34
|
+
: pendingInfo.sourceUrl === props.url
|
|
35
|
+
: false
|
|
36
|
+
|
|
37
|
+
if (pendingInfo?.sourceUrl && pendingMatchesRoute) {
|
|
38
|
+
info = pendingInfo
|
|
39
|
+
store.setPendingPlayInfo(null)
|
|
40
|
+
await store.ready
|
|
41
|
+
const historyByPath = info.path
|
|
42
|
+
? store.getHistoryInfoByPath(info.path)
|
|
43
|
+
: undefined
|
|
44
|
+
const historyByUrl = store.getHistoryInfo(info.sourceUrl)
|
|
45
|
+
const shouldUseHistory =
|
|
46
|
+
(info.currentTime ?? 0) <= 0 && (info.duration ?? 0) <= 0
|
|
47
|
+
if (shouldUseHistory) {
|
|
48
|
+
const historyInfo = historyByPath || historyByUrl
|
|
49
|
+
if (historyInfo) {
|
|
50
|
+
info = historyInfo
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
if (pendingInfo?.sourceUrl && !pendingMatchesRoute) {
|
|
55
|
+
store.setPendingPlayInfo(null)
|
|
56
|
+
}
|
|
57
|
+
await store.ready
|
|
58
|
+
if (props.path) {
|
|
59
|
+
info = store.getHistoryInfoByPath(props.path)
|
|
60
|
+
} else {
|
|
61
|
+
info = store.getHistoryInfo(props.url)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!info) {
|
|
65
|
+
info = {
|
|
66
|
+
_id: "",
|
|
67
|
+
sourceUrl: props.url,
|
|
68
|
+
name: props.name || "",
|
|
69
|
+
duration: 0,
|
|
70
|
+
currentTime: 0,
|
|
71
|
+
invalid: false,
|
|
72
|
+
fromNetdisk: props.fromNetdisk == "true",
|
|
73
|
+
path: props.path || "",
|
|
74
|
+
updateTime: undefined,
|
|
75
|
+
subtitles: undefined,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
player.trigger({ type: "openVideo", info })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const onInit = (player: NativePlayerAPI) => {
|
|
82
|
+
if (boundPlayer === player) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
if (boundPlayer && openVideoHandler) {
|
|
86
|
+
boundPlayer.off("openVideo", openVideoHandler)
|
|
87
|
+
}
|
|
88
|
+
if (unwatchUrl) {
|
|
89
|
+
unwatchUrl()
|
|
90
|
+
unwatchUrl = null
|
|
91
|
+
}
|
|
92
|
+
boundPlayer = player
|
|
93
|
+
openVideoHandler = (e?: any) => {
|
|
94
|
+
const info = e?.info as VideoInfo | undefined
|
|
95
|
+
if (!info?.sourceUrl) return
|
|
96
|
+
const current = router.currentRoute.value
|
|
97
|
+
if (
|
|
98
|
+
current.name === "link" &&
|
|
99
|
+
current.query.url === info.sourceUrl &&
|
|
100
|
+
current.query.name === info.name
|
|
101
|
+
) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
router.replace({
|
|
105
|
+
name: "link",
|
|
106
|
+
query: {
|
|
107
|
+
url: info.sourceUrl,
|
|
108
|
+
name: info.name || "",
|
|
109
|
+
path: info.path || undefined,
|
|
110
|
+
fromNetdisk: info.fromNetdisk ? "true" : undefined,
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
player.on("openVideo", openVideoHandler)
|
|
115
|
+
trigger(player)
|
|
116
|
+
unwatchUrl = watch(
|
|
117
|
+
() => props.url,
|
|
118
|
+
() => {
|
|
119
|
+
trigger(player)
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
onUnmounted(() => {
|
|
125
|
+
if (boundPlayer && openVideoHandler) {
|
|
126
|
+
boundPlayer.off("openVideo", openVideoHandler)
|
|
127
|
+
}
|
|
128
|
+
if (unwatchUrl) {
|
|
129
|
+
unwatchUrl()
|
|
130
|
+
unwatchUrl = null
|
|
131
|
+
}
|
|
132
|
+
boundPlayer = null
|
|
133
|
+
openVideoHandler = null
|
|
134
|
+
})
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<template>
|
|
138
|
+
<div class="w-full h-full min-h-0 flex flex-col bg-black">
|
|
139
|
+
<NativeVideoPlayer @back="back" :onInit="onInit" :poster="''" />
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
// Scope utility classes to player root to avoid affecting host app styles.
|
|
4
|
+
important: ".lzc-video-player",
|
|
5
|
+
// Disable Tailwind preflight to prevent global reset leakage in library mode.
|
|
6
|
+
corePlugins: {
|
|
7
|
+
preflight: false,
|
|
8
|
+
container: false,
|
|
9
|
+
},
|
|
10
|
+
content: ["./src/**/*.vue"],
|
|
11
|
+
theme: {
|
|
12
|
+
extend: {},
|
|
13
|
+
},
|
|
14
|
+
plugins: [],
|
|
15
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@vue/tsconfig/tsconfig.web.json",
|
|
3
|
+
"include": ["env.d.ts", "src/**/*.d.ts", "src/**/*", "src/**/*.vue"],
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"target": "es2017",
|
|
8
|
+
"baseUrl": ".",
|
|
9
|
+
"paths": {
|
|
10
|
+
"@/*": ["./src/*"]
|
|
11
|
+
},
|
|
12
|
+
"types": ["vite-plugin-svg-icons/client", "vite-plugin-pwa/client"]
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
"references": [
|
|
16
|
+
{
|
|
17
|
+
"path": "./tsconfig.config.json"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { fileURLToPath, URL } from "node:url"
|
|
2
|
+
|
|
3
|
+
import { defineConfig } from "vite"
|
|
4
|
+
import vue from "@vitejs/plugin-vue"
|
|
5
|
+
import { quasar, transformAssetUrls } from "@quasar/vite-plugin"
|
|
6
|
+
import { createSvgIconsPlugin } from "vite-plugin-svg-icons"
|
|
7
|
+
import dts from "vite-plugin-dts"
|
|
8
|
+
import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"
|
|
9
|
+
import path from "node:path"
|
|
10
|
+
import remToPx from "@thedutchcoder/postcss-rem-to-px"
|
|
11
|
+
import tailwindcss from "tailwindcss"
|
|
12
|
+
|
|
13
|
+
const version = process.env["VERSION"] || "0.0.0-dev"
|
|
14
|
+
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
define: {
|
|
17
|
+
"process.env.NODE_ENV": '"production"',
|
|
18
|
+
__APP_VERSION__: JSON.stringify(version),
|
|
19
|
+
},
|
|
20
|
+
publicDir: false,
|
|
21
|
+
css: {
|
|
22
|
+
postcss: {
|
|
23
|
+
plugins: [tailwindcss(), remToPx({ baseValue: 10 })],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
build: {
|
|
27
|
+
outDir: "lib/dist",
|
|
28
|
+
lib: {
|
|
29
|
+
// Could also be a dictionary or array of multiple entry points
|
|
30
|
+
entry: "src/lzc-video-player.ts",
|
|
31
|
+
name: "lzc-video-player",
|
|
32
|
+
fileName: "lzc-video-player",
|
|
33
|
+
// the proper extensions will be added
|
|
34
|
+
formats: ["es"],
|
|
35
|
+
},
|
|
36
|
+
rollupOptions: {
|
|
37
|
+
external: ["vue", "pinia"],
|
|
38
|
+
// make sure to externalize deps that shouldn't be bundled
|
|
39
|
+
// into your library
|
|
40
|
+
output: {
|
|
41
|
+
// Provide global variables to use in the UMD build
|
|
42
|
+
// for externalized deps
|
|
43
|
+
globals: {
|
|
44
|
+
vue: "Vue",
|
|
45
|
+
pinia: "pinia",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
plugins: [
|
|
51
|
+
vue({
|
|
52
|
+
template: {
|
|
53
|
+
transformAssetUrls,
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
quasar({
|
|
57
|
+
sassVariables: "./src/quasar-variables.sass",
|
|
58
|
+
}),
|
|
59
|
+
createSvgIconsPlugin({
|
|
60
|
+
// 指定需要缓存的图标文件夹
|
|
61
|
+
iconDirs: [path.resolve(process.cwd(), "./src/icons")],
|
|
62
|
+
// 指定symbolId格式
|
|
63
|
+
symbolId: "icon-[name].svg",
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 自定义插入位置
|
|
67
|
+
* @default: body-last
|
|
68
|
+
*/
|
|
69
|
+
inject: "body-last",
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* custom dom id
|
|
73
|
+
* @default: __svg__icons__dom__
|
|
74
|
+
*/
|
|
75
|
+
customDomId: "__lzc_video_player_svg_dom__",
|
|
76
|
+
}),
|
|
77
|
+
cssInjectedByJsPlugin(),
|
|
78
|
+
dts({
|
|
79
|
+
exclude: ["src/views", "src/router", "src/main.ts", "src/App.vue"],
|
|
80
|
+
outDir: "lib/dist",
|
|
81
|
+
}),
|
|
82
|
+
],
|
|
83
|
+
resolve: {
|
|
84
|
+
alias: {
|
|
85
|
+
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
})
|