ytptube/ui/components/VideoPlayer.vue
2025-02-28 01:06:58 +03:00

211 lines
4.6 KiB
Vue

<style>
:root {
--plyr-captions-background: rgba(0, 0, 0, 0.6);
--plyr-captions-text-color: #f3db4d;
--webkit-text-track-display: none;
}
.plyr__caption {
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
font-size: 140%;
font-weight: bold;
}
.plyr--full-ui ::-webkit-media-text-track-container {
display: var(--webkit-text-track-display);
}
</style>
<template>
<div>
<video id="player" ref="video" :poster="thumbnail" :title="title" playsinline>
<source v-for="source in sources" :key="source.src" :src="source.src" @error="source.onerror"
:type="source.type" />
<track v-for="track in tracks" :key="track.file" :kind="track.kind" :label="track.label" :srclang="track.lang"
:src="track.file" default />
</video>
</div>
</template>
<script setup>
import { onMounted, onUpdated, ref, defineProps, defineEmits, onUnmounted } from 'vue'
import Hls from 'hls.js'
import Plyr from 'plyr'
import 'plyr/dist/plyr.css'
import { makeDownload } from '~/utils/index'
const config = useConfigStore()
const props = defineProps({
item: {
type: Object,
default: () => ({})
},
})
const emitter = defineEmits(['closeModel'])
const video = ref(null)
const tracks = ref([])
const sources = ref([])
const thumbnail = ref('')
const artist = ref('')
const title = ref('')
const isAudio = ref(false)
const isApple = /(iPhone|iPod|iPad).*AppleWebKit/i.test(navigator.userAgent)
let player = null;
let hls = null;
const eventFunc = e => {
if (e.key === 'Escape') {
emitter('closeModel')
}
}
onMounted(async () => {
const response = await (await request(makeDownload(config, props.item, 'api/file/info'))).json()
if (props.item.extras?.thumbnail) {
thumbnail.value = '/api/thumbnail?url=' + encodePath(props.item.extras.thumbnail)
}
sources.value.push({
src: makeDownload(config, props.item, isApple ? 'm3u8' : 'api/download'),
type: isApple ? response.mimetype : 'application/x-mpegURL',
onerror: e => src_error(e),
})
if (props.item.extras?.channel) {
artist.value = props.item.extras.channel
}
if (!artist.value && props.item.extras?.uploader) {
artist.value = props.item.extras.uploader
}
if (props.item?.title) {
title.value = props.item.title
}
if (!props.item.extras?.is_video && props.item.extras?.is_audio) {
isAudio.value = true
}
response.sidecar.forEach((cap, id) => {
tracks.value.push({
kind: "captions",
label: cap.name,
lang: cap.lang,
file: `${makeDownload(config, { filename: cap.file }, 'api/player/subtitle')}.vtt`
})
})
if (isApple) {
document.documentElement.style.setProperty('--webkit-text-track-display', 'block');
}
prepareVideoPlayer()
window.addEventListener('keydown', eventFunc)
})
onUpdated(() => prepareVideoPlayer())
onUnmounted(() => {
if (player) {
player.destroy()
}
if (hls) {
hls.destroy()
}
window.removeEventListener('keydown', eventFunc)
if (title.value) {
window.document.title = 'YTPTube'
}
})
const prepareVideoPlayer = () => {
let mediaMetadata = {
title: title.value,
};
if (thumbnail.value) {
mediaMetadata['artwork'] = [
{ src: thumbnail.value, sizes: '1920x1080', type: 'image/jpeg' },
]
}
if (artist.value) {
mediaMetadata['artist'] = artist.value
}
let opts = {
debug: false,
clickToPlay: true,
keyboard: { focused: true, global: true },
controls: [
'play-large', 'play', 'progress', 'current-time', 'duration', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen'
],
fullscreen: {
enabled: true,
fallback: true,
iosNative: true,
},
storage: {
enabled: true,
key: 'plyr'
},
artist: artist.value,
mediaMetadata: mediaMetadata,
captions: {
update: true,
},
};
if (artist.value) {
opts.artist = artist.value
}
if (title.value) {
opts.title = title.value
}
if (thumbnail.value) {
opts.poster = thumbnail.value
}
player = new Plyr(video.value, opts);
player.source = {
type: isAudio.value ? 'audio' : 'video',
title: title.value,
poster: thumbnail.value,
};
if (title.value) {
window.document.title = `YTPTube - Playing: ${title.value}`
}
}
const src_error = () => {
if (hls) {
return
}
console.warn('Direct play failed, trying HLS.');
attach_hls(makeDownload(config, props.item, 'm3u8'));
}
const attach_hls = link => {
hls = new Hls({
debug: false,
enableWorker: true,
lowLatencyMode: true,
backBufferLength: 90,
fragLoadingTimeOut: 200000,
});
hls.loadSource(link)
hls.attachMedia(video.value)
}
</script>