refactor: restructure project to standard Go layout
- Reorganize code into cmd/bilidown and internal/ packages - Rename client/ to web/ for frontend source - Remove systray dependency for headless web service - Embed static files into binary using go:embed - Update import paths to use internal/ prefix - Update .gitignore with common patterns Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
import { VanComponent } from '../mixin'
|
||||
import van from 'vanjs-core'
|
||||
import { Modal } from 'bootstrap'
|
||||
import Plyr from 'plyr'
|
||||
import 'plyr/dist/plyr.css'
|
||||
|
||||
const { a, button, div, video, audio } = van.tags
|
||||
|
||||
export class PlayerComp implements VanComponent {
|
||||
element: HTMLElement
|
||||
/** 使用该对象前,请先将元素加入文档树后调用 `initPlayer` 方法 */
|
||||
player!: Plyr
|
||||
videoElement: HTMLVideoElement
|
||||
audioElement: HTMLAudioElement
|
||||
videoPlayer!: Plyr
|
||||
audioPlayer!: Plyr
|
||||
type = van.state<'video' | 'audio'>('video')
|
||||
src = van.state('')
|
||||
filename = van.state('')
|
||||
|
||||
constructor() {
|
||||
this.videoElement = video()
|
||||
this.audioElement = audio()
|
||||
this.element = this.Root()
|
||||
}
|
||||
|
||||
Root(): HTMLElement {
|
||||
const that = this
|
||||
return div({ class: 'player-container' },
|
||||
div({ class: 'video-wrapper', hidden: () => that.type.val !== 'video' }, this.videoElement),
|
||||
div({ class: 'audio-wrapper', hidden: () => that.type.val !== 'audio' }, this.audioElement)
|
||||
)
|
||||
}
|
||||
|
||||
initPlayer() {
|
||||
if (!this.element.parentNode) throw new Error('请将播放器元素加入 DOM 树')
|
||||
this.videoPlayer = new Plyr(this.videoElement, {})
|
||||
this.audioPlayer = new Plyr(this.audioElement, {})
|
||||
// 设置当前活动的player
|
||||
this.updateActivePlayer()
|
||||
}
|
||||
|
||||
private updateActivePlayer() {
|
||||
this.player = this.type.val === 'video' ? this.videoPlayer : this.audioPlayer
|
||||
}
|
||||
|
||||
setType(type: 'video' | 'audio') {
|
||||
this.type.val = type
|
||||
this.updateActivePlayer()
|
||||
// 清除非活动元素的src
|
||||
if (type === 'video') {
|
||||
this.audioElement.src = ''
|
||||
} else {
|
||||
this.videoElement.src = ''
|
||||
}
|
||||
}
|
||||
|
||||
setSrc(src: string) {
|
||||
this.src.val = src
|
||||
if (this.type.val === 'video') {
|
||||
this.videoElement.src = src
|
||||
this.audioElement.src = ''
|
||||
} else {
|
||||
this.audioElement.src = src
|
||||
this.videoElement.src = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PlayerModalComp implements VanComponent {
|
||||
element: HTMLElement
|
||||
playerComp: PlayerComp
|
||||
modal: Modal
|
||||
type = van.state<'video' | 'audio'>('video')
|
||||
title = van.state('视频播放器')
|
||||
fileExtension = van.state('.mp4')
|
||||
|
||||
constructor() {
|
||||
this.playerComp = new PlayerComp()
|
||||
this.element = this.Root()
|
||||
this.initModalEvent()
|
||||
|
||||
van.add(document.body, this.element)
|
||||
this.playerComp.initPlayer()
|
||||
this.modal = new Modal(this.element)
|
||||
}
|
||||
|
||||
initModalEvent() {
|
||||
this.element.addEventListener('shown.bs.modal', () => {
|
||||
this.playerComp.player.play()
|
||||
})
|
||||
|
||||
this.element.addEventListener('hide.bs.modal', () => {
|
||||
if (!this.playerComp.player.stopped) {
|
||||
this.playerComp.player.stop()
|
||||
this.playerComp.setSrc('')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
open(src: string, filename: string, type: 'video' | 'audio') {
|
||||
this.type.val = type
|
||||
this.title.val = type === 'video' ? '视频播放器' : '音频播放器'
|
||||
this.fileExtension.val = type === 'video' ? '.mp4' : '.m4a'
|
||||
this.playerComp.setType(type)
|
||||
this.playerComp.setSrc(src)
|
||||
this.playerComp.filename.val = filename
|
||||
this.modal.show()
|
||||
}
|
||||
|
||||
Root() {
|
||||
const that = this
|
||||
return div({ class: `modal fade`, tabIndex: -1 },
|
||||
div({ class: 'modal-dialog modal-xl modal-fullscreen-xl-down' },
|
||||
div({ class: `modal-content` },
|
||||
div({ class: `modal-header` },
|
||||
div({ class: `h5 modal-title` }, () => this.title.val),
|
||||
button({ class: `btn-close`, 'data-bs-dismiss': `modal` })
|
||||
),
|
||||
div({ class: 'modal-body p-0' },
|
||||
div({ class: () => that.type.val === 'video' ? 'ratio ratio-16x9' : '' }, this.playerComp.element),
|
||||
div({ class: 'vstack p-3 gap-3' },
|
||||
div({}, that.playerComp.filename)
|
||||
)
|
||||
),
|
||||
div({ class: 'modal-footer' },
|
||||
button({ class: 'btn btn-secondary', 'data-bs-dismiss': `modal` }, '关闭'),
|
||||
button({
|
||||
class: 'btn btn-primary', onclick() {
|
||||
const link = a({
|
||||
download: () => that.playerComp.filename.val + that.fileExtension.val,
|
||||
href: that.playerComp.src
|
||||
})
|
||||
that.modal.hide()
|
||||
link.click()
|
||||
}
|
||||
}, '下载')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user