import { Box } from "@hope-ui/solid" import { createSignal, onCleanup, onMount } from "solid-js" import { useRouter, useLink } from "~/hooks" import { getMainColor, getSettingBool, objStore } from "~/store" import { ObjType } from "~/types" import { ext, pathDir, pathJoin } from "~/utils" import Artplayer from "artplayer" import { type Option } from "artplayer/types/option" import { type Setting } from "artplayer/types/setting" import { type Events } from "artplayer/types/events" import artplayerPluginDanmuku from "artplayer-plugin-danmuku" import artplayerPluginAss from "~/components/artplayer-plugin-ass" import mpegts from "mpegts.js" import Hls from "hls.js" import { currentLang } from "~/app/i18n" import { AutoHeightPlugin, VideoBox } from "./video_box" import { ArtPlayerIconsSubtitle } from "~/components/icons" import { useNavigate } from "@solidjs/router" const Preview = () => { const { pathname, searchParams } = useRouter() const { proxyLink } = useLink() const navigate = useNavigate() let videos = objStore.objs.filter((obj) => obj.type === ObjType.VIDEO) if (videos.length === 0) { videos = [objStore.obj] } const next_video = () => { const index = videos.findIndex((f) => f.name === objStore.obj.name) if (index < videos.length - 1) { navigate( pathJoin(pathDir(location.pathname), videos[index + 1].name) + "?auto_fullscreen=" + player.fullscreen, ) } } const previous_video = () => { const index = videos.findIndex((f) => f.name === objStore.obj.name) if (index > 0) { navigate( pathJoin(pathDir(location.pathname), videos[index - 1].name) + "?auto_fullscreen=" + player.fullscreen, ) } } let player: Artplayer let flvPlayer: mpegts.Player let hlsPlayer: Hls let option: Option = { id: pathname(), container: "#video-player", url: objStore.raw_url, title: objStore.obj.name, volume: 0.5, autoplay: getSettingBool("video_autoplay"), autoSize: false, autoMini: true, loop: false, flip: true, playbackRate: true, aspectRatio: true, screenshot: true, setting: true, hotkey: true, pip: true, mutex: true, fullscreen: true, fullscreenWeb: true, subtitleOffset: true, miniProgressBar: false, playsInline: true, theme: getMainColor(), // layers: [], // settings: [], // contextmenu: [], controls: [ { name: "previous-button", index: 10, position: "left", html: '', tooltip: "Previous", click: function () { previous_video() }, }, { name: "next-button", index: 11, position: "left", html: '', tooltip: "Next", click: function () { next_video() }, }, ], quality: [], // highlight: [], plugins: [AutoHeightPlugin], whitelist: [], settings: [], // subtitle:{} moreVideoAttr: { // @ts-ignore "webkit-playsinline": true, playsInline: true, crossOrigin: "anonymous", }, type: ext(objStore.obj.name), customType: { flv: function (video: HTMLMediaElement, url: string) { flvPlayer = mpegts.createPlayer( { type: "flv", url: url, }, { referrerPolicy: "same-origin" }, ) flvPlayer.attachMediaElement(video) flvPlayer.load() }, m3u8: function (video: HTMLMediaElement, url: string) { hlsPlayer = new Hls() hlsPlayer.loadSource(url) hlsPlayer.attachMedia(video) if (!video.src) { video.src = url } }, }, lang: ["en", "zh-cn", "zh-tw"].includes(currentLang().toLowerCase()) ? (currentLang().toLowerCase() as string) : "en", lock: true, fastForward: true, autoPlayback: true, autoOrientation: true, airplay: true, } const subtitle = objStore.related.filter((obj) => { for (const ext of [".srt", ".ass", ".vtt"]) { if (obj.name.endsWith(ext)) { return true } } return false }) const danmu = objStore.related.find((obj) => { for (const ext of [".xml"]) { if (obj.name.endsWith(ext)) { return true } } return false }) // TODO: add a switch in manage panel to choose whether to enable `libass-wasm` const enableEnhanceAss = true if (subtitle.length != 0) { let isEnhanceAssMode = false // set default subtitle const defaultSubtitle = subtitle[0] if (enableEnhanceAss && ext(defaultSubtitle.name).toLowerCase() === "ass") { isEnhanceAssMode = true option.plugins?.push( artplayerPluginAss({ // debug: true, subUrl: proxyLink(defaultSubtitle, true), }), ) } else { option.subtitle = { url: proxyLink(defaultSubtitle, true), type: ext(defaultSubtitle.name), escape: false, } } // render subtitle toggle menu const innerMenu: Setting[] = [ { id: "setting_subtitle_display", html: "Display", tooltip: "Show", switch: true, onSwitch: function (item: Setting) { item.tooltip = item.switch ? "Hide" : "Show" setSubtitleVisible(!item.switch) // sync menu subtitle tooltip const menu_sub = option.settings?.find( (_) => _.id === "setting_subtitle", ) menu_sub && (menu_sub.tooltip = item.tooltip) return !item.switch }, }, ] subtitle.forEach((item, i) => { innerMenu.push({ default: i === 0, html: ( {item.name} ) as HTMLElement, name: item.name, url: proxyLink(item, true), }) }) option.settings?.push({ id: "setting_subtitle", html: "Subtitle", tooltip: "Show", icon: ArtPlayerIconsSubtitle({ size: 24 }) as HTMLElement, selector: innerMenu, onSelect: function (item: Setting) { if (enableEnhanceAss && ext(item.name).toLowerCase() === "ass") { isEnhanceAssMode = true this.emit("artplayer-plugin-ass:switch" as keyof Events, item.url) setSubtitleVisible(true) } else { isEnhanceAssMode = false this.subtitle.switch(item.url, { name: item.name }) this.once("subtitleLoad", setSubtitleVisible.bind(this, true)) } const switcher = innerMenu.find( (_) => _.id === "setting_subtitle_display", ) if (switcher && !switcher.switch) switcher.$html?.click?.() // sync from display switcher return switcher?.tooltip }, }) function setSubtitleVisible(visible: boolean) { const type = isEnhanceAssMode ? "ass" : "webvtt" switch (type) { case "ass": player.subtitle.show = false player.emit("artplayer-plugin-ass:visible" as keyof Events, visible) break case "webvtt": default: player.subtitle.show = visible player.emit("artplayer-plugin-ass:visible" as keyof Events, false) break } } } if (danmu) { option.plugins?.push( artplayerPluginDanmuku({ danmuku: proxyLink(danmu, true), speed: 5, opacity: 1, fontSize: 25, color: "#FFFFFF", mode: 0, margin: [0, "0%"], antiOverlap: false, synchronousPlayback: false, lockTime: 5, maxLength: 100, theme: "dark", heatmap: true, }), ) } onMount(() => { player = new Artplayer(option) let auto_fullscreen: boolean switch (searchParams["auto_fullscreen"]) { case "true": auto_fullscreen = true break case "false": auto_fullscreen = false break default: auto_fullscreen = false } player.on("ready", () => { player.fullscreen = auto_fullscreen }) player.on("video:ended", () => { if (!autoNext()) return next_video() }) player.on("error", () => { if (player.video.crossOrigin) { console.log( "Error detected. Trying to remove Cross-Origin attribute. Screenshot may not be available.", ) player.video.crossOrigin = null } }) }) onCleanup(() => { if (player && player.video) player.video.src = "" player?.destroy() flvPlayer?.destroy() hlsPlayer?.destroy() }) const [autoNext, setAutoNext] = createSignal() return ( ) } export default Preview