make do without tauri and test vid
s0f44589ws that starts to work s6df44a1first video sdacb6cdclick to time s7831ae4remove tauri s2d18c9bconcurrently and frontmatter sdc43284add new video and test s0dd03affix for youtube format and markdown compat sc30fa38remove placeholder mds s d8f1a54 remove images from git
This commit is contained in:
1 parent
fc58e68ad7
commit
00033e0341
38 files changed
+4897
-6257
No files matched your search
+12
-37
@@ -10,16 +10,15 @@ export default defineConfig({
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Examples', link: '/markdown-examples' }
|
||||
],
|
||||
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Examples',
|
||||
items: [
|
||||
{ text: 'Markdown Examples', link: '/markdown-examples' },
|
||||
{ text: 'Runtime API Examples', link: '/api-examples' }
|
||||
]
|
||||
text: 'Preview',
|
||||
// items: [
|
||||
// { text: 'Markdown Examples', link: '/markdown-examples' },
|
||||
// { text: 'Runtime API Examples', link: '/api-examples' }
|
||||
// ]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -29,38 +28,14 @@ export default defineConfig({
|
||||
},
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
server: {
|
||||
// make sure this port matches the devUrl port in tauri.conf.json file
|
||||
port: 5173,
|
||||
// Tauri expects a fixed port, fail if that port is not available
|
||||
strictPort: true,
|
||||
// if the host Tauri is expecting is set, use it
|
||||
host: TAURI_HOST || false,
|
||||
hmr: TAURI_HOST
|
||||
? {
|
||||
protocol: 'ws',
|
||||
host: TAURI_HOST,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
|
||||
watch: {
|
||||
// tell vite to ignore watching `src-tauri`
|
||||
ignored: ['**/src-tauri/**'],
|
||||
server: {
|
||||
proxy: {
|
||||
'/socket': {
|
||||
target: 'ws://localhost:8080',
|
||||
ws: true,
|
||||
rewriteWsOrigin: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
// Env variables starting with the item of `envPrefix` will be exposed in tauri's source code through `import.meta.env`.
|
||||
envPrefix: ['VITE_', 'TAURI_ENV_*'],
|
||||
build: {
|
||||
// Tauri uses Chromium on Windows and WebKit on macOS and Linux
|
||||
target:
|
||||
process.env.TAURI_ENV_PLATFORM == 'windows'
|
||||
? 'chrome105'
|
||||
: 'safari13',
|
||||
// don't minify for debug builds
|
||||
minify: !process.env.TAURI_ENV_DEBUG ? 'esbuild' : false,
|
||||
// produce sourcemaps for debug builds
|
||||
sourcemap: !!process.env.TAURI_ENV_DEBUG,
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -1,69 +1,107 @@
|
||||
<template>
|
||||
<form @submit="onSubmit">
|
||||
<input type="text" v-model="q">
|
||||
<button type="submit">Submit</button>
|
||||
<form @submit="onSubmit">
|
||||
<input type="text" v-model="q" />
|
||||
<button type="submit">Submit</button>
|
||||
|
||||
<video controls ref="video" :src="videoSrc" @loadeddata="onVideoLoad" @seeked="onSeeked"></video>
|
||||
</form>
|
||||
<video
|
||||
v-if="videoSrc"
|
||||
controls
|
||||
ref="video"
|
||||
:src="videoSrc"
|
||||
@loadeddata="onVideoLoad"
|
||||
></video>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, useTemplateRef } from 'vue'
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { ref, useTemplateRef } from "vue";
|
||||
|
||||
const q = ref('')
|
||||
const videoSrc = ref('')
|
||||
const videoEl = useTemplateRef('video')
|
||||
const q = ref("");
|
||||
const videoSrc = ref("");
|
||||
const videoEl = useTemplateRef("video");
|
||||
|
||||
function onSubmit(ev: SubmitEvent) {
|
||||
ev.preventDefault()
|
||||
ev.preventDefault();
|
||||
if (!q.value) return;
|
||||
|
||||
if (!q.value) return
|
||||
|
||||
videoSrc.value = '/' + q.value
|
||||
}
|
||||
|
||||
interface SeekQEvent {
|
||||
t: number
|
||||
videoSrc.value = "/" + q.value;
|
||||
}
|
||||
|
||||
function onVideoLoad() {
|
||||
invoke('download_q', { q: q.value.replace(/\.[a-z0-9]+$/, '') })
|
||||
const ws = new WebSocket("ws://localhost:8080");
|
||||
|
||||
listen<SeekQEvent>('seek_q', (ev) => {
|
||||
videoEl.value!.currentTime = ev.payload.t;
|
||||
})
|
||||
const vid = videoEl.value!;
|
||||
|
||||
const q_value = q.value;
|
||||
|
||||
ws.onmessage = (ev) => {
|
||||
const { name, ts } = JSON.parse(ev.data);
|
||||
switch (name) {
|
||||
case "seek_q_ts": (async () => {
|
||||
for (const t of ts) {
|
||||
const b = await new Promise<Blob | null>((resolve) => {
|
||||
vid.onseeked = () => {
|
||||
const v = videoEl.value!;
|
||||
let c = document.createElement("canvas");
|
||||
c.height = v.videoHeight || parseInt(v.style.height);
|
||||
c.width = v.videoWidth || parseInt(v.style.width);
|
||||
const ctx = c.getContext("2d")!;
|
||||
ctx.drawImage(v, 0, 0);
|
||||
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
name: "timed_q",
|
||||
q: q_value,
|
||||
t,
|
||||
}),
|
||||
);
|
||||
|
||||
c.toBlob((b) => resolve(b));
|
||||
}
|
||||
|
||||
if (typeof t === 'string'){
|
||||
const [min, sec] = t.split('_');
|
||||
vid.currentTime = parseInt(min) * 60 + parseInt(sec) + 1;
|
||||
} else {
|
||||
vid.currentTime = t;
|
||||
}
|
||||
})
|
||||
if (b) ws.send(b);
|
||||
vid.onseeked = null;
|
||||
}
|
||||
ws.send(JSON.stringify({ name: 'done', q: q_value }));
|
||||
})(); break;
|
||||
}
|
||||
};
|
||||
|
||||
ws.onopen = async () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
name: "download_q",
|
||||
q: q_value,
|
||||
duration: vid.duration,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
};
|
||||
}
|
||||
|
||||
function onSeeked() {
|
||||
const v = videoEl.value!
|
||||
let c = document.createElement('canvas');
|
||||
c.height = v.videoHeight || parseInt(v.style.height);
|
||||
c.width = v.videoWidth || parseInt(v.style.width);
|
||||
const ctx = c.getContext('2d')!;
|
||||
ctx.drawImage(v, 0, 0);
|
||||
|
||||
invoke('timed_q', { q: q.value.replace(/\.[a-z0-9]+$/, ''), t: videoEl.value!.currentTime, data: c.toDataURL('image/png') })
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
form {
|
||||
margin: 0.5em 0;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
form * {
|
||||
border: 1px solid gray;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 0.5em;
|
||||
cursor: pointer;
|
||||
padding: 0 0.5em;
|
||||
border-radius: 5px;
|
||||
margin-left: 0.5em;
|
||||
cursor: pointer;
|
||||
padding: 0 0.5em;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<video v-if="videoURL" controls :src="videoURL" ref="video">
|
||||
Hello
|
||||
</video>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress'
|
||||
import { computed, useTemplateRef, watch } from 'vue'
|
||||
|
||||
const { frontmatter } = useData<{ video?: string }>()
|
||||
const videoURL = computed(() => frontmatter.value?.video)
|
||||
const videoEl = useTemplateRef("video");
|
||||
|
||||
function setImgClick(){
|
||||
const v = videoEl.value;
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelectorAll('img').forEach((img) => {
|
||||
const [, min, sec] = /^(\d+)\:(\d+)$/.exec(img.alt) || []
|
||||
const t = min && sec ? parseInt(min) * 60 + parseInt(sec) : null
|
||||
if (!t) return;
|
||||
|
||||
img.onclick = () => {
|
||||
v.currentTime = t
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(videoURL, () => {
|
||||
setImgClick()
|
||||
})
|
||||
|
||||
setTimeout(() => setImgClick(), 500)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
video {
|
||||
width: 750px;
|
||||
overflow: visible;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,17 @@
|
||||
import type { Theme } from "vitepress";
|
||||
import DefaultTheme from "vitepress/theme";
|
||||
import { h } from 'vue'
|
||||
|
||||
import Downloader from "./components/Downloader.vue";
|
||||
import Video from './components/Video.vue'
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout() {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
'sidebar-nav-after': () => h(Video)
|
||||
})
|
||||
},
|
||||
enhanceApp({ app }) {
|
||||
// register your custom global components
|
||||
app.component("Downloader", Downloader);
|
||||
|
||||
Reference in new issue
Block a user