YouTube JS API seek
This commit is contained in:
1 parent
218ba874f9
commit
d9777c3c82
6 files changed
+99
-12
No files matched your search
@@ -4,15 +4,26 @@ import { execSync } from "node:child_process";
|
|||||||
const host = process.env.TAURI_DEV_HOST;
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
IMG_ORIGIN = `https://git.polv.cc/polv/yt2press/raw/branch/${
|
IMG_ORIGIN = `https://git.polv.cc/polv/yt2press/raw/branch/${execSync(
|
||||||
execSync("git branch --show-current").toString().trimEnd()
|
"git branch --show-current",
|
||||||
}/docs/public`,
|
)
|
||||||
|
.toString()
|
||||||
|
.trimEnd()}/docs/public`,
|
||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
// https://vitepress.dev/reference/site-config
|
// https://vitepress.dev/reference/site-config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
title: "yt2press",
|
title: "yt2press",
|
||||||
description: "video to vitepress",
|
description: "video to vitepress",
|
||||||
|
head: [
|
||||||
|
[
|
||||||
|
"script",
|
||||||
|
{
|
||||||
|
async: "async",
|
||||||
|
src: "https://www.youtube.com/iframe_api",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
// https://vitepress.dev/reference/default-theme-config
|
// https://vitepress.dev/reference/default-theme-config
|
||||||
nav: [{ text: "Home", link: "/" }],
|
nav: [{ text: "Home", link: "/" }],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
<YouTube v-if="youtubeID" :id="youtubeID" />
|
<YouTube ref="yt" v-if="youtubeID" :id="youtubeID" />
|
||||||
<video
|
<video
|
||||||
v-else-if="videoURL"
|
v-else-if="videoURL"
|
||||||
controls
|
controls
|
||||||
@@ -21,6 +21,7 @@ import YouTube from "./YouTube.vue";
|
|||||||
const { frontmatter } = useData<{ video?: string }>();
|
const { frontmatter } = useData<{ video?: string }>();
|
||||||
const videoURL = computed(() => frontmatter.value?.video);
|
const videoURL = computed(() => frontmatter.value?.video);
|
||||||
const videoEl = useTemplateRef("video");
|
const videoEl = useTemplateRef("video");
|
||||||
|
const ytEl = useTemplateRef("yt");
|
||||||
|
|
||||||
const youtubeID = ref("");
|
const youtubeID = ref("");
|
||||||
|
|
||||||
@@ -41,14 +42,15 @@ function imgClickListener(ev: PointerEvent) {
|
|||||||
if (!(ev.target instanceof HTMLImageElement)) return;
|
if (!(ev.target instanceof HTMLImageElement)) return;
|
||||||
const img = ev.target;
|
const img = ev.target;
|
||||||
|
|
||||||
if (!videoEl.value) return;
|
|
||||||
const v = videoEl.value;
|
|
||||||
|
|
||||||
const [, min, sec] = /^(\d+)\:(\d+)$/.exec(img.alt) || [];
|
const [, min, sec] = /^(\d+)\:(\d+)$/.exec(img.alt) || [];
|
||||||
const t = min && sec ? parseInt(min) * 60 + parseInt(sec) : null;
|
const t = min && sec ? parseInt(min) * 60 + parseInt(sec) : null;
|
||||||
if (!t) return;
|
if (!t) return;
|
||||||
|
|
||||||
v.currentTime = t;
|
if (videoEl.value) {
|
||||||
|
videoEl.value.currentTime = t;
|
||||||
|
} else if (ytEl.value) {
|
||||||
|
ytEl.value.seekTo(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(videoURL, () => {
|
watch(videoURL, () => {
|
||||||
@@ -59,6 +61,7 @@ function onVideoNotFound() {
|
|||||||
const m = /\[(.+?)\]\.\S+$/.exec(videoURL.value || "") || [];
|
const m = /\[(.+?)\]\.\S+$/.exec(videoURL.value || "") || [];
|
||||||
youtubeID.value = m[1] || "";
|
youtubeID.value = m[1] || "";
|
||||||
}
|
}
|
||||||
|
onVideoNotFound();
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const { SSR } = import.meta.env;
|
const { SSR } = import.meta.env;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
<iframe
|
<iframe
|
||||||
|
id="yt-player"
|
||||||
:class="$style.iframe"
|
:class="$style.iframe"
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
:src="'https://www.youtube.com/embed/' + $props.id"
|
:src="url.href"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
allow="
|
allow="
|
||||||
accelerometer;
|
accelerometer;
|
||||||
@@ -20,7 +21,29 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{ id: string; title?: string }>();
|
import { onMounted, ref } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{ id: string; title?: string }>();
|
||||||
|
|
||||||
|
defineExpose({ seekTo });
|
||||||
|
|
||||||
|
const url = new URL("https://www.youtube.com/embed/" + props.id);
|
||||||
|
url.searchParams.set("enablejsapi", "1");
|
||||||
|
|
||||||
|
const player = ref();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const { YT } = window as any;
|
||||||
|
if (YT?.Player) {
|
||||||
|
player.value = new YT.Player("yt-player");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function seekTo(t: number) {
|
||||||
|
if (player.value) {
|
||||||
|
player.value.seekTo(t, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module>
|
<style module>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
body {
|
/* body {
|
||||||
padding-top: env(safe-area-inset-top);
|
padding-top: env(safe-area-inset-top);
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
padding-left: env(safe-area-inset-left);
|
padding-left: env(safe-area-inset-left);
|
||||||
padding-right: env(safe-area-inset-right);
|
padding-right: env(safe-area-inset-right);
|
||||||
}
|
} */
|
||||||
@@ -4,6 +4,12 @@ dedup: 0.05
|
|||||||
video: '/一家馬桶公司,為什麼成了AI命脈?TOTO埋了50年的暗棋 [gfx_EnxmOEY].webm'
|
video: '/一家馬桶公司,為什麼成了AI命脈?TOTO埋了50年的暗棋 [gfx_EnxmOEY].webm'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
你以為 TOTO 只是一家做馬桶的公司?事實上,它花了近 50 年,把最擅長的「燒陶瓷」一路做到半導體產業最深處。
|
||||||
|
|
||||||
|
一塊不起眼的靜電吸盤,如今成了晶圓製造不可或缺的關鍵零件。當 AI 浪潮席捲全球,這家百年馬桶公司,也意外站上了科技產業的風口。本期就來看看,TOTO 如何用一門看似無聊的手藝,埋下一顆等待了半個世紀的暗棋。
|
||||||
|
|
||||||
|
## AI 報告,為何拉升一支「馬桶股」?
|
||||||
|
|
||||||

|

|
||||||
0:00
|
0:00
|
||||||
|
|
||||||
@@ -253,6 +259,7 @@ video: '/一家馬桶公司,為什麼成了AI命脈?TOTO埋了50年的暗棋
|
|||||||

|

|
||||||
1:28
|
1:28
|
||||||
|
|
||||||
|
## 馬桶界的愛馬仕:TOTO 的前兩場豪賭
|
||||||
|
|
||||||

|

|
||||||
1:30
|
1:30
|
||||||
@@ -1080,6 +1087,8 @@ video: '/一家馬桶公司,為什麼成了AI命脈?TOTO埋了50年的暗棋
|
|||||||
<br/>
|
<br/>
|
||||||
7:04
|
7:04
|
||||||
|
|
||||||
|
## 第三場豪賭:不能只靠一條腿走路
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
7:06
|
7:06
|
||||||
@@ -1531,6 +1540,8 @@ video: '/一家馬桶公司,為什麼成了AI命脈?TOTO埋了50年的暗棋
|
|||||||

|

|
||||||
10:12
|
10:12
|
||||||
|
|
||||||
|
## 半導體首戰慘敗,卻留下關鍵技術
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
10:14
|
10:14
|
||||||
@@ -2014,6 +2025,7 @@ video: '/一家馬桶公司,為什麼成了AI命脈?TOTO埋了50年的暗棋
|
|||||||

|

|
||||||
13:36
|
13:36
|
||||||
|
|
||||||
|
## 靜電吸盤:一塊陶瓷如何卡住晶圓廠
|
||||||
|
|
||||||

|

|
||||||
13:38
|
13:38
|
||||||
@@ -2489,6 +2501,8 @@ video: '/一家馬桶公司,為什麼成了AI命脈?TOTO埋了50年的暗棋
|
|||||||

|

|
||||||
16:46
|
16:46
|
||||||
|
|
||||||
|
## AI 點火:五十年的暗棋終於爆發
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
16:48
|
16:48
|
||||||
@@ -2913,6 +2927,8 @@ video: '/一家馬桶公司,為什麼成了AI命脈?TOTO埋了50年的暗棋
|
|||||||

|

|
||||||
19:38
|
19:38
|
||||||
|
|
||||||
|
## 中國市場失血:TOTO 的冰與火
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
19:40
|
19:40
|
||||||
@@ -3330,6 +3346,7 @@ video: '/一家馬桶公司,為什麼成了AI命脈?TOTO埋了50年的暗棋
|
|||||||

|

|
||||||
22:14
|
22:14
|
||||||
|
|
||||||
|
## 不追風口,為什麼反而等到了風口?
|
||||||
|
|
||||||

|

|
||||||
22:16
|
22:16
|
||||||
|
|||||||
@@ -1,11 +1,44 @@
|
|||||||
package cc.polv.yt2press
|
package cc.polv.yt2press
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.webkit.WebView
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
|
||||||
class MainActivity : TauriActivity() {
|
class MainActivity : TauriActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val rootView: View = findViewById(android.R.id.content)
|
||||||
|
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
|
||||||
|
val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
|
||||||
|
|
||||||
|
val bottomPadding = if (imeVisible) imeHeight else systemBars.bottom
|
||||||
|
|
||||||
|
v.setPadding(
|
||||||
|
systemBars.left,
|
||||||
|
systemBars.top,
|
||||||
|
systemBars.right,
|
||||||
|
bottomPadding
|
||||||
|
)
|
||||||
|
insets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWebViewCreate(webView: WebView) {
|
||||||
|
super.onWebViewCreate(webView)
|
||||||
|
|
||||||
|
webView.setBackgroundColor(0x00000000)
|
||||||
|
|
||||||
|
webView.fitsSystemWindows = true
|
||||||
|
|
||||||
|
android.util.Log.i("MainActivity", "WebView created, waiting for Rust hide() call...")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user