Compare commits
3 Commits
64184b7a28
...
faf0634924
Author | SHA1 | Date | |
---|---|---|---|
![]() |
faf0634924 | ||
![]() |
6d4c44427c | ||
![]() |
5795061602 |
@@ -3,7 +3,7 @@
|
|||||||
<!-- Connection Status -->
|
<!-- Connection Status -->
|
||||||
<div class="absolute top-4 right-4 z-10">
|
<div class="absolute top-4 right-4 z-10">
|
||||||
<div class="flex items-center gap-2 px-3 py-1 rounded-full text-sm" :class="connectionStatusClass">
|
<div class="flex items-center gap-2 px-3 py-1 rounded-full text-sm" :class="connectionStatusClass">
|
||||||
<div class="w-2 h-2 rounded-full" :class="connectionDotClass"></div>
|
<div class="w-2 h-2 rounded-full" :class="connectionDotClass" />
|
||||||
{{ connectionStatus }}
|
{{ connectionStatus }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<video
|
<video
|
||||||
v-if="currentVideo"
|
v-if="currentVideo"
|
||||||
ref="videoElement"
|
ref="videoElement"
|
||||||
class="w-full h-full object-contain"
|
class="w-full h-full object-contain bg-black"
|
||||||
:src="videoSrc"
|
:src="videoSrc"
|
||||||
:volume="volume / 100"
|
:volume="volume / 100"
|
||||||
:loop="isLooping"
|
:loop="isLooping"
|
||||||
@@ -47,20 +47,22 @@
|
|||||||
|
|
||||||
<!-- Loading Animation -->
|
<!-- Loading Animation -->
|
||||||
<div v-if="isConnecting" class="flex items-center space-x-1">
|
<div v-if="isConnecting" class="flex items-center space-x-1">
|
||||||
<div class="w-2 h-2 bg-gray-500 rounded-full animate-pulse"></div>
|
<div class="w-2 h-2 bg-gray-500 rounded-full animate-pulse" />
|
||||||
<div class="w-2 h-2 bg-gray-500 rounded-full animate-pulse" style="animation-delay: 0.2s"></div>
|
<div class="w-2 h-2 bg-gray-500 rounded-full animate-pulse" style="animation-delay: 0.2s" />
|
||||||
<div class="w-2 h-2 bg-gray-500 rounded-full animate-pulse" style="animation-delay: 0.4s"></div>
|
<div class="w-2 h-2 bg-gray-500 rounded-full animate-pulse" style="animation-delay: 0.4s" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Overlay Controls (hidden by default, shown on hover if enabled) -->
|
<!-- Overlay Controls (hidden by default, shown on hover if enabled) -->
|
||||||
<div v-if="showControls && currentVideo"
|
<div
|
||||||
class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity duration-300">
|
v-if="showControls && currentVideo"
|
||||||
|
class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity duration-300"
|
||||||
|
>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<button @click="togglePlay" class="p-3 rounded-full bg-white bg-opacity-20 hover:bg-opacity-30 transition-all">
|
<button class="p-3 rounded-full bg-white bg-opacity-20 hover:bg-opacity-30 transition-all" @click="togglePlay">
|
||||||
<UIcon :name="isPlaying ? 'lucide:pause' : 'lucide:play'" class="w-8 h-8" />
|
<UIcon :name="isPlaying ? 'lucide:pause' : 'lucide:play'" class="w-8 h-8" />
|
||||||
</button>
|
</button>
|
||||||
<button @click="stop" class="p-3 rounded-full bg-white bg-opacity-20 hover:bg-opacity-30 transition-all">
|
<button class="p-3 rounded-full bg-white bg-opacity-20 hover:bg-opacity-30 transition-all" @click="stop">
|
||||||
<UIcon name="lucide:square" class="w-8 h-8" />
|
<UIcon name="lucide:square" class="w-8 h-8" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,456 +72,487 @@
|
|||||||
<!-- Debug Info (only in dev mode) -->
|
<!-- Debug Info (only in dev mode) -->
|
||||||
<div v-if="isDev" class="absolute bottom-4 left-4 text-xs text-gray-500 space-y-1">
|
<div v-if="isDev" class="absolute bottom-4 left-4 text-xs text-gray-500 space-y-1">
|
||||||
<div>Volume: {{ volume }}%</div>
|
<div>Volume: {{ volume }}%</div>
|
||||||
<div v-if="currentVideo">Position: {{ Math.floor(position) }}s / {{ Math.floor(duration) }}s</div>
|
<div v-if="currentVideo">
|
||||||
|
Position: {{ Math.floor(position) }}s / {{ Math.floor(duration) }}s
|
||||||
|
</div>
|
||||||
<div>Loop: {{ isLooping ? 'On' : 'Off' }}</div>
|
<div>Loop: {{ isLooping ? 'On' : 'Off' }}</div>
|
||||||
<div>Fullscreen: {{ isFullscreen ? 'On' : 'Off' }}</div>
|
<div>Fullscreen: {{ isFullscreen ? 'On' : 'Off' }}</div>
|
||||||
|
<div v-if="videoElement">
|
||||||
|
Video: {{ videoElement.videoWidth }}x{{ videoElement.videoHeight }}
|
||||||
|
</div>
|
||||||
|
<div v-if="currentVideo">
|
||||||
|
Src: {{ videoSrc }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
// remove direct import that breaks vite in dev; we will fallback to file:// URLs
|
// remove direct import that breaks vite in dev; we will fallback to file:// URLs
|
||||||
const appConfig = useAppConfig()
|
const appConfig = useAppConfig();
|
||||||
const isDev = process.env.NODE_ENV === 'development'
|
const isDev = process.env.NODE_ENV === "development";
|
||||||
|
|
||||||
// Tauri functions will be loaded dynamically
|
// Tauri functions will be loaded dynamically
|
||||||
let invoke = null
|
let invoke = null;
|
||||||
let listen = null
|
let listen = null;
|
||||||
|
|
||||||
// Player state
|
// Player state
|
||||||
const connectionStatus = ref('Disconnected')
|
const connectionStatus = ref("Disconnected");
|
||||||
const isConnecting = ref(false)
|
const isConnecting = ref(false);
|
||||||
const currentVideo = ref(null)
|
const currentVideo = ref(null);
|
||||||
const isPlaying = ref(false)
|
const isPlaying = ref(false);
|
||||||
const position = ref(0)
|
const position = ref(0);
|
||||||
const duration = ref(0)
|
const duration = ref(0);
|
||||||
const volume = ref(appConfig.player.defaultVolume)
|
const volume = ref(appConfig.player.defaultVolume);
|
||||||
const isLooping = ref(false)
|
const isLooping = ref(false);
|
||||||
const isFullscreen = ref(false)
|
const isFullscreen = ref(false);
|
||||||
const showControls = ref(false)
|
const showControls = ref(false);
|
||||||
const isMuted = ref(true)
|
const isMuted = ref(true);
|
||||||
|
|
||||||
// Video element ref
|
// Video element ref
|
||||||
const videoElement = ref(null)
|
const videoElement = ref(null);
|
||||||
|
|
||||||
// Video source URL - reactive ref that gets updated when currentVideo changes
|
// Video source URL - reactive ref that gets updated when currentVideo changes
|
||||||
const videoSrc = ref('')
|
const videoSrc = ref("");
|
||||||
|
|
||||||
// Convert file path to Tauri-compatible URL
|
// Convert file path to Tauri-compatible URL
|
||||||
async function updateVideoSrc() {
|
async function updateVideoSrc() {
|
||||||
const raw = currentVideo.value?.path || ''
|
const raw = currentVideo.value?.path || "";
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
console.log('🎥 No video path available')
|
console.log("🎥 No video path available");
|
||||||
videoSrc.value = ''
|
videoSrc.value = "";
|
||||||
return
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Windows paths with backslashes and drive letters
|
|
||||||
const isWindowsPath = /^[a-zA-Z]:[\\/]|^\\/.test(raw)
|
|
||||||
|
|
||||||
if (isWindowsPath) {
|
|
||||||
// Windows: Use file:// protocol for local file access
|
|
||||||
// Convert backslashes to forward slashes and use file:// protocol
|
|
||||||
let normalizedPath = raw.replace(/\\/g, '/')
|
|
||||||
|
|
||||||
// Ensure proper file:// format with drive letter
|
|
||||||
if (!normalizedPath.startsWith('/')) {
|
|
||||||
normalizedPath = '/' + normalizedPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove duplicate slashes
|
|
||||||
normalizedPath = normalizedPath.replace(/\/+/g, '/')
|
|
||||||
|
|
||||||
videoSrc.value = `file://${normalizedPath}`
|
|
||||||
} else {
|
|
||||||
// Unix-like: Use file:// protocol for absolute paths
|
|
||||||
if (raw.startsWith('/')) {
|
|
||||||
videoSrc.value = `file://${raw}`
|
|
||||||
} else {
|
|
||||||
// Use relative HTTP URL for bundled resources
|
|
||||||
const filename = raw.split(/[/\\]/).pop() || "video.mp4";
|
|
||||||
if (filename === 'video.mp4') {
|
|
||||||
videoSrc.value = '/video.mp4'
|
|
||||||
} else {
|
|
||||||
videoSrc.value = `/${filename}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🎥 Using URL for video:')
|
|
||||||
console.log(' - Original path:', raw)
|
|
||||||
console.log(' - Platform:', isWindowsPath ? 'Windows' : 'Unix')
|
|
||||||
console.log(' - Final URL:', videoSrc.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for changes in currentVideo and update videoSrc
|
// Check if we're running in Tauri
|
||||||
watch(currentVideo, updateVideoSrc, { immediate: true })
|
const isTauri = typeof window !== "undefined" && window.__TAURI_INTERNALS__;
|
||||||
|
|
||||||
// Watch for changes in videoSrc to debug
|
if (isTauri) {
|
||||||
watch(videoSrc, (newSrc, oldSrc) => {
|
try {
|
||||||
console.log('🎥 videoSrc changed:')
|
// Use Tauri's convertFileSrc to get proper URL for local files
|
||||||
console.log(' - Old:', oldSrc)
|
const { convertFileSrc } = await import("@tauri-apps/api/core");
|
||||||
console.log(' - New:', newSrc)
|
videoSrc.value = convertFileSrc(raw);
|
||||||
})
|
console.log("🎥 Using Tauri convertFileSrc for video:", videoSrc.value);
|
||||||
|
} catch (error) {
|
||||||
// Computed properties
|
console.error("❌ Failed to use Tauri convertFileSrc:", error);
|
||||||
const connectionStatusClass = computed(() => {
|
// Fallback to HTTP URL for bundled resources
|
||||||
switch (connectionStatus.value) {
|
if (raw.includes("video.mp4") || raw.endsWith("video.mp4")) {
|
||||||
case 'Connected':
|
videoSrc.value = "/video.mp4";
|
||||||
return 'bg-green-900 bg-opacity-50 text-green-300'
|
|
||||||
case 'Connecting':
|
|
||||||
return 'bg-yellow-900 bg-opacity-50 text-yellow-300'
|
|
||||||
default:
|
|
||||||
return 'bg-red-900 bg-opacity-50 text-red-300'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const connectionDotClass = computed(() => {
|
|
||||||
switch (connectionStatus.value) {
|
|
||||||
case 'Connected':
|
|
||||||
return 'bg-green-400'
|
|
||||||
case 'Connecting':
|
|
||||||
return 'bg-yellow-400 animate-pulse'
|
|
||||||
default:
|
|
||||||
return 'bg-red-400'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const statusMessage = computed(() => {
|
|
||||||
if (isConnecting.value) {
|
|
||||||
return 'Connecting to controller...'
|
|
||||||
}
|
|
||||||
if (connectionStatus.value === 'Connected') {
|
|
||||||
return 'Ready to play. Waiting for controller commands...'
|
|
||||||
}
|
|
||||||
return 'Waiting for controller connection...'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Video control functions
|
|
||||||
function togglePlay() {
|
|
||||||
if (!videoElement.value) return
|
|
||||||
|
|
||||||
if (isPlaying.value) {
|
|
||||||
videoElement.value.pause()
|
|
||||||
} else {
|
|
||||||
videoElement.value.play()
|
|
||||||
}
|
|
||||||
isPlaying.value = !isPlaying.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function stop() {
|
|
||||||
if (!videoElement.value) return
|
|
||||||
|
|
||||||
videoElement.value.pause()
|
|
||||||
videoElement.value.currentTime = 0
|
|
||||||
isPlaying.value = false
|
|
||||||
position.value = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function seek(time) {
|
|
||||||
if (!videoElement.value) return
|
|
||||||
|
|
||||||
videoElement.value.currentTime = time
|
|
||||||
position.value = time
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVolume(newVolume) {
|
|
||||||
volume.value = Math.max(0, Math.min(100, newVolume))
|
|
||||||
if (videoElement.value) {
|
|
||||||
videoElement.value.volume = volume.value / 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Video event handlers
|
|
||||||
function onVideoLoaded() {
|
|
||||||
console.log('🎬 Video loaded event triggered')
|
|
||||||
if (videoElement.value) {
|
|
||||||
duration.value = videoElement.value.duration
|
|
||||||
videoElement.value.volume = volume.value / 100
|
|
||||||
console.log('🎬 Video metadata:', {
|
|
||||||
duration: duration.value,
|
|
||||||
volume: volume.value,
|
|
||||||
currentSrc: videoElement.value.currentSrc,
|
|
||||||
readyState: videoElement.value.readyState
|
|
||||||
})
|
|
||||||
// 尝试主动播放;若被策略阻止则静音后重试
|
|
||||||
console.log('🎬 Attempting to play video...')
|
|
||||||
|
|
||||||
// Always start muted to comply with autoplay policies, then unmute
|
|
||||||
isMuted.value = true
|
|
||||||
|
|
||||||
// Small delay to ensure video is ready
|
|
||||||
setTimeout(() => {
|
|
||||||
if (videoElement.value) {
|
|
||||||
const playPromise = videoElement.value.play()
|
|
||||||
if (playPromise && typeof playPromise.then === 'function') {
|
|
||||||
playPromise.then(() => {
|
|
||||||
console.log('✅ Video started playing successfully (muted)')
|
|
||||||
isPlaying.value = true
|
|
||||||
|
|
||||||
// Try to unmute after successful playback
|
|
||||||
setTimeout(() => {
|
|
||||||
if (videoElement.value) {
|
|
||||||
videoElement.value.muted = false
|
|
||||||
isMuted.value = false
|
|
||||||
console.log('🔊 Video unmuted')
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error('❌ Autoplay failed:', err)
|
|
||||||
// Show user a play button if autoplay fails
|
|
||||||
showControls.value = true
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
// Fallback for browsers without promise-based play
|
const filename = raw.split(/[/\\]/).pop() || "video.mp4";
|
||||||
try {
|
videoSrc.value = `/${filename}`;
|
||||||
videoElement.value.play()
|
|
||||||
console.log('✅ Video started playing (fallback)')
|
|
||||||
isPlaying.value = true
|
|
||||||
} catch (err) {
|
|
||||||
console.error('❌ Autoplay failed (fallback):', err)
|
|
||||||
showControls.value = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 500)
|
} else {
|
||||||
}
|
// In web browser, use HTTP URLs
|
||||||
}
|
const filename = raw.split(/[/\\]/).pop() || "video.mp4";
|
||||||
|
if (filename === "video.mp4") {
|
||||||
|
videoSrc.value = "/video.mp4";
|
||||||
|
} else {
|
||||||
|
videoSrc.value = `/${filename}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onTimeUpdate() {
|
console.log("🎥 Using URL for video:");
|
||||||
if (videoElement.value) {
|
console.log(" - Original path:", raw);
|
||||||
position.value = videoElement.value.currentTime
|
console.log(" - Platform:", isTauri ? "Tauri" : "Web");
|
||||||
|
console.log(" - Final URL:", videoSrc.value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function onVideoEnded() {
|
// Watch for changes in currentVideo and update videoSrc
|
||||||
isPlaying.value = false
|
watch(currentVideo, updateVideoSrc, { immediate: true });
|
||||||
|
|
||||||
|
// Watch for changes in videoSrc to debug
|
||||||
|
watch(videoSrc, (newSrc, oldSrc) => {
|
||||||
|
console.log("🎥 videoSrc changed:");
|
||||||
|
console.log(" - Old:", oldSrc);
|
||||||
|
console.log(" - New:", newSrc);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Computed properties
|
||||||
|
const connectionStatusClass = computed(() => {
|
||||||
|
switch (connectionStatus.value) {
|
||||||
|
case "Connected":
|
||||||
|
return "bg-green-900 bg-opacity-50 text-green-300";
|
||||||
|
case "Connecting":
|
||||||
|
return "bg-yellow-900 bg-opacity-50 text-yellow-300";
|
||||||
|
default:
|
||||||
|
return "bg-red-900 bg-opacity-50 text-red-300";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const connectionDotClass = computed(() => {
|
||||||
|
switch (connectionStatus.value) {
|
||||||
|
case "Connected":
|
||||||
|
return "bg-green-400";
|
||||||
|
case "Connecting":
|
||||||
|
return "bg-yellow-400 animate-pulse";
|
||||||
|
default:
|
||||||
|
return "bg-red-400";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusMessage = computed(() => {
|
||||||
|
if (isConnecting.value) {
|
||||||
|
return "Connecting to controller...";
|
||||||
|
}
|
||||||
|
if (connectionStatus.value === "Connected") {
|
||||||
|
return "Ready to play. Waiting for controller commands...";
|
||||||
|
}
|
||||||
|
return "Waiting for controller connection...";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Video control functions
|
||||||
|
function togglePlay() {
|
||||||
|
if (!videoElement.value) return;
|
||||||
|
|
||||||
|
if (isPlaying.value) {
|
||||||
|
videoElement.value.pause();
|
||||||
|
} else {
|
||||||
|
videoElement.value.play();
|
||||||
|
}
|
||||||
|
isPlaying.value = !isPlaying.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
if (!videoElement.value) return;
|
||||||
|
|
||||||
|
videoElement.value.pause();
|
||||||
|
videoElement.value.currentTime = 0;
|
||||||
|
isPlaying.value = false;
|
||||||
|
position.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function seek(time) {
|
||||||
|
if (!videoElement.value) return;
|
||||||
|
|
||||||
|
videoElement.value.currentTime = time;
|
||||||
|
position.value = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVolume(newVolume) {
|
||||||
|
volume.value = Math.max(0, Math.min(100, newVolume));
|
||||||
|
if (videoElement.value) {
|
||||||
|
videoElement.value.volume = volume.value / 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video event handlers
|
||||||
|
function onVideoLoaded() {
|
||||||
|
console.log("🎬 Video loaded event triggered");
|
||||||
|
if (videoElement.value) {
|
||||||
|
duration.value = videoElement.value.duration;
|
||||||
|
videoElement.value.volume = volume.value / 100;
|
||||||
|
console.log("🎬 Video metadata:", {
|
||||||
|
duration: duration.value,
|
||||||
|
volume: volume.value,
|
||||||
|
currentSrc: videoElement.value.currentSrc,
|
||||||
|
readyState: videoElement.value.readyState
|
||||||
|
});
|
||||||
|
// 尝试主动播放;若被策略阻止则静音后重试
|
||||||
|
console.log("🎬 Attempting to play video...");
|
||||||
|
|
||||||
|
// Always start muted to comply with autoplay policies, then unmute
|
||||||
|
isMuted.value = true;
|
||||||
|
|
||||||
|
// Small delay to ensure video is ready
|
||||||
|
setTimeout(() => {
|
||||||
|
if (videoElement.value) {
|
||||||
|
const playPromise = videoElement.value.play();
|
||||||
|
if (playPromise && typeof playPromise.then === "function") {
|
||||||
|
playPromise.then(() => {
|
||||||
|
console.log("✅ Video started playing successfully (muted)");
|
||||||
|
isPlaying.value = true;
|
||||||
|
|
||||||
|
// Try to unmute after successful playback
|
||||||
|
setTimeout(() => {
|
||||||
|
if (videoElement.value) {
|
||||||
|
videoElement.value.muted = false;
|
||||||
|
isMuted.value = false;
|
||||||
|
console.log("🔊 Video unmuted");
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error("❌ Autoplay failed:", err);
|
||||||
|
// Show user a play button if autoplay fails
|
||||||
|
showControls.value = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback for browsers without promise-based play
|
||||||
|
try {
|
||||||
|
videoElement.value.play();
|
||||||
|
console.log("✅ Video started playing (fallback)");
|
||||||
|
isPlaying.value = true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Autoplay failed (fallback):", err);
|
||||||
|
showControls.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTimeUpdate() {
|
||||||
|
if (videoElement.value) {
|
||||||
|
position.value = videoElement.value.currentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onVideoEnded() {
|
||||||
|
isPlaying.value = false;
|
||||||
// Send playback finished event to controller
|
// Send playback finished event to controller
|
||||||
// This will be implemented when WebSocket is ready
|
// This will be implemented when WebSocket is ready
|
||||||
}
|
|
||||||
|
|
||||||
function onVideoError(event) {
|
|
||||||
console.error('❌ Video playback error:', event)
|
|
||||||
console.error('❌ Video error details:', {
|
|
||||||
error: event.target?.error,
|
|
||||||
networkState: event.target?.networkState,
|
|
||||||
readyState: event.target?.readyState,
|
|
||||||
currentSrc: event.target?.currentSrc
|
|
||||||
})
|
|
||||||
currentVideo.value = null
|
|
||||||
isPlaying.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize fullscreen if configured
|
|
||||||
onMounted(async () => {
|
|
||||||
console.log('🔧 Component mounted, initializing...')
|
|
||||||
console.log('📁 Current directory:', window.location.href)
|
|
||||||
|
|
||||||
if (appConfig.player.autoFullscreen) {
|
|
||||||
console.log('🔧 Auto fullscreen enabled')
|
|
||||||
// Request fullscreen
|
|
||||||
nextTick(() => {
|
|
||||||
document.documentElement.requestFullscreen?.() ||
|
|
||||||
document.documentElement.webkitRequestFullscreen?.()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup Tauri event listeners
|
function onVideoError(event) {
|
||||||
console.log('🔧 Setting up Tauri listeners...')
|
console.error("❌ Video playback error:", event);
|
||||||
await setupTauriListeners()
|
console.error("❌ Video error details:", {
|
||||||
|
error: event.target?.error,
|
||||||
// Get initial player state
|
networkState: event.target?.networkState,
|
||||||
console.log('🔧 Getting initial player state...')
|
readyState: event.target?.readyState,
|
||||||
await updatePlayerState()
|
currentSrc: event.target?.currentSrc
|
||||||
|
});
|
||||||
console.log('✅ Component initialization complete')
|
currentVideo.value = null;
|
||||||
|
isPlaying.value = false;
|
||||||
// Additional debug: check if video.mp4 exists in public folder
|
}
|
||||||
try {
|
|
||||||
const response = await fetch('/video.mp4', { method: 'HEAD' })
|
// Initialize fullscreen if configured
|
||||||
if (response.ok) {
|
onMounted(async () => {
|
||||||
console.log('📹 Found video.mp4 in public folder via HTTP')
|
console.log("🔧 Component mounted, initializing...");
|
||||||
} else {
|
console.log("📁 Current directory:", window.location.href);
|
||||||
console.log('❌ video.mp4 not accessible via HTTP')
|
|
||||||
|
if (appConfig.player.autoFullscreen) {
|
||||||
|
console.log("🔧 Auto fullscreen enabled");
|
||||||
|
// Request fullscreen
|
||||||
|
nextTick(() => {
|
||||||
|
document.documentElement.requestFullscreen?.()
|
||||||
|
|| document.documentElement.webkitRequestFullscreen?.();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.log('🌐 Cannot check video.mp4 via HTTP (expected in Tauri)')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Setup Tauri event listeners
|
// Setup Tauri event listeners
|
||||||
async function setupTauriListeners() {
|
console.log("🔧 Setting up Tauri listeners...");
|
||||||
try {
|
await setupTauriListeners();
|
||||||
// Load Tauri APIs dynamically
|
|
||||||
const { invoke: tauriInvoke } = await import('@tauri-apps/api/core')
|
|
||||||
const { listen: tauriListen } = await import('@tauri-apps/api/event')
|
|
||||||
|
|
||||||
// Set global references
|
|
||||||
invoke = tauriInvoke
|
|
||||||
listen = tauriListen
|
|
||||||
|
|
||||||
// Listen for connection status changes
|
|
||||||
await listen('connection-status', (event) => {
|
|
||||||
connectionStatus.value = event.payload === 'connected' ? 'Connected' : 'Disconnected'
|
|
||||||
isConnecting.value = false
|
|
||||||
})
|
|
||||||
|
|
||||||
// Listen for player commands from backend
|
|
||||||
await listen('player-command', async (event) => {
|
|
||||||
console.log('📡 Backend player command received:', event.payload)
|
|
||||||
const command = event.payload
|
|
||||||
await handlePlayerCommand(command)
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('✅ Tauri event listeners setup complete')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Failed to setup Tauri listeners:', error)
|
|
||||||
// Fallback for development mode
|
|
||||||
setTimeout(() => {
|
|
||||||
connectionStatus.value = 'Waiting for WebSocket server...'
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update player state from backend
|
// Get initial player state
|
||||||
async function updatePlayerState() {
|
console.log("🔧 Getting initial player state...");
|
||||||
if (!invoke) {
|
await updatePlayerState();
|
||||||
console.warn('Tauri invoke function not available yet')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const state = await invoke('get_player_state')
|
|
||||||
|
|
||||||
currentVideo.value = state.current_video
|
|
||||||
isPlaying.value = state.playback_status === 'playing'
|
|
||||||
position.value = state.position
|
|
||||||
duration.value = state.duration
|
|
||||||
volume.value = state.volume
|
|
||||||
isLooping.value = state.is_looping
|
|
||||||
isFullscreen.value = state.is_fullscreen
|
|
||||||
connectionStatus.value = state.connection_status === 'connected' ? 'Connected' : 'Disconnected'
|
|
||||||
console.log('✅ Player state updated:', state)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Failed to get player state:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle player commands from WebSocket
|
console.log("✅ Component initialization complete");
|
||||||
async function handlePlayerCommand(command) {
|
|
||||||
console.log('🎮 Received player command:', command)
|
// Additional debug: check if video.mp4 exists in public folder
|
||||||
switch (command.type) {
|
try {
|
||||||
case 'play':
|
const response = await fetch("/video.mp4", { method: "HEAD" });
|
||||||
|
if (response.ok) {
|
||||||
|
console.log("📹 Found video.mp4 in public folder via HTTP");
|
||||||
|
} else {
|
||||||
|
console.log("❌ video.mp4 not accessible via HTTP");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("🌐 Cannot check video.mp4 via HTTP (expected in Tauri)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup Tauri event listeners
|
||||||
|
async function setupTauriListeners() {
|
||||||
|
try {
|
||||||
|
// Load Tauri APIs dynamically
|
||||||
|
const { invoke: tauriInvoke } = await import("@tauri-apps/api/core");
|
||||||
|
const { listen: tauriListen } = await import("@tauri-apps/api/event");
|
||||||
|
|
||||||
|
// Set global references
|
||||||
|
invoke = tauriInvoke;
|
||||||
|
listen = tauriListen;
|
||||||
|
|
||||||
|
// Listen for connection status changes
|
||||||
|
await listen("connection-status", (event) => {
|
||||||
|
connectionStatus.value = event.payload === "connected" ? "Connected" : "Disconnected";
|
||||||
|
isConnecting.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for player commands from backend
|
||||||
|
await listen("player-command", async (event) => {
|
||||||
|
console.log("📡 Backend player command received:", event.payload);
|
||||||
|
const command = event.payload;
|
||||||
|
await handlePlayerCommand(command);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("✅ Tauri event listeners setup complete");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Failed to setup Tauri listeners:", error);
|
||||||
|
// Fallback for development mode
|
||||||
|
setTimeout(() => {
|
||||||
|
connectionStatus.value = "Waiting for WebSocket server...";
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update player state from backend
|
||||||
|
async function updatePlayerState() {
|
||||||
|
if (!invoke) {
|
||||||
|
console.warn("Tauri invoke function not available yet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const state = await invoke("get_player_state");
|
||||||
|
|
||||||
|
currentVideo.value = state.current_video;
|
||||||
|
isPlaying.value = state.playback_status === "playing";
|
||||||
|
position.value = state.position;
|
||||||
|
duration.value = state.duration;
|
||||||
|
volume.value = state.volume;
|
||||||
|
isLooping.value = state.is_looping;
|
||||||
|
isFullscreen.value = state.is_fullscreen;
|
||||||
|
connectionStatus.value = state.connection_status === "connected" ? "Connected" : "Disconnected";
|
||||||
|
console.log("✅ Player state updated:", state);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Failed to get player state:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle player commands from WebSocket
|
||||||
|
async function handlePlayerCommand(command) {
|
||||||
|
console.log("🎮 Received player command:", command);
|
||||||
|
switch (command.type) {
|
||||||
|
case "play":
|
||||||
if (videoElement.value) {
|
if (videoElement.value) {
|
||||||
try {
|
try {
|
||||||
// Ensure video is ready before attempting to play
|
// Ensure video is ready before attempting to play
|
||||||
if (videoElement.value.readyState >= 2) {
|
if (videoElement.value.readyState >= 2) {
|
||||||
await videoElement.value.play()
|
await videoElement.value.play();
|
||||||
isPlaying.value = true
|
isPlaying.value = true;
|
||||||
} else {
|
} else {
|
||||||
// Wait for video to be ready
|
// Wait for video to be ready
|
||||||
videoElement.value.addEventListener('canplay', () => {
|
videoElement.value.addEventListener("canplay", () => {
|
||||||
videoElement.value.play().catch(console.error)
|
videoElement.value.play().catch(console.error);
|
||||||
isPlaying.value = true
|
isPlaying.value = true;
|
||||||
}, { once: true })
|
}, { once: true });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Play error:', error)
|
console.error("Play error:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break;
|
||||||
case 'pause':
|
case "pause":
|
||||||
if (videoElement.value) {
|
if (videoElement.value) {
|
||||||
videoElement.value.pause()
|
videoElement.value.pause();
|
||||||
isPlaying.value = false
|
isPlaying.value = false;
|
||||||
}
|
}
|
||||||
break
|
break;
|
||||||
case 'stop':
|
case "stop":
|
||||||
stop()
|
stop();
|
||||||
break
|
break;
|
||||||
case 'seek':
|
case "seek":
|
||||||
seek(command.position)
|
seek(command.position);
|
||||||
break
|
break;
|
||||||
case 'setVolume':
|
case "setVolume":
|
||||||
setVolume(command.volume)
|
setVolume(command.volume);
|
||||||
break
|
break;
|
||||||
case 'setLoop':
|
case "setLoop":
|
||||||
isLooping.value = command.enabled
|
isLooping.value = command.enabled;
|
||||||
if (videoElement.value) {
|
if (videoElement.value) {
|
||||||
videoElement.value.loop = command.enabled
|
videoElement.value.loop = command.enabled;
|
||||||
}
|
}
|
||||||
break
|
break;
|
||||||
case 'toggleFullscreen':
|
case "toggleFullscreen":
|
||||||
toggleFullscreenMode()
|
toggleFullscreenMode();
|
||||||
break
|
break;
|
||||||
case 'loadVideo':
|
case "loadVideo":
|
||||||
await loadVideoFromPath(command.path)
|
await loadVideoFromPath(command.path);
|
||||||
break
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
// Update state after command
|
|
||||||
await updatePlayerState()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load video from file path
|
|
||||||
async function loadVideoFromPath(path) {
|
|
||||||
console.log('📥 loadVideoFromPath called with:', path)
|
|
||||||
try {
|
|
||||||
const rawPath = path.startsWith('file://') ? path.slice(7) : path
|
|
||||||
console.log('📥 Processing video path:', { originalPath: path, rawPath })
|
|
||||||
|
|
||||||
const title = rawPath.split('/').pop() || rawPath
|
|
||||||
currentVideo.value = {
|
|
||||||
path: rawPath,
|
|
||||||
title,
|
|
||||||
duration: null,
|
|
||||||
size: null,
|
|
||||||
format: null
|
|
||||||
}
|
}
|
||||||
console.log('📹 currentVideo.value set to:', currentVideo.value)
|
|
||||||
|
// Update state after command
|
||||||
// The videoSrc will be updated automatically via the watcher
|
await updatePlayerState();
|
||||||
|
}
|
||||||
if (invoke) {
|
|
||||||
await invoke('load_video', { path: rawPath })
|
// Load video from file path
|
||||||
|
async function loadVideoFromPath(path) {
|
||||||
|
console.log("📥 loadVideoFromPath called with:", path);
|
||||||
|
try {
|
||||||
|
const rawPath = path.startsWith("file://") ? path.slice(7) : path;
|
||||||
|
console.log("📥 Processing video path:", { originalPath: path, rawPath });
|
||||||
|
|
||||||
|
const title = rawPath.split("/").pop() || rawPath;
|
||||||
|
currentVideo.value = {
|
||||||
|
path: rawPath,
|
||||||
|
title,
|
||||||
|
duration: null,
|
||||||
|
size: null,
|
||||||
|
format: null
|
||||||
|
};
|
||||||
|
console.log("📹 currentVideo.value set to:", currentVideo.value);
|
||||||
|
|
||||||
|
// The videoSrc will be updated automatically via the watcher
|
||||||
|
|
||||||
|
if (invoke) {
|
||||||
|
await invoke("load_video", { path: rawPath });
|
||||||
|
}
|
||||||
|
console.log("📹 Video loaded successfully:", title);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Failed to load video:", error);
|
||||||
}
|
}
|
||||||
console.log('📹 Video loaded successfully:', title)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Failed to load video:', error)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle fullscreen mode
|
// Toggle fullscreen mode
|
||||||
function toggleFullscreenMode() {
|
function toggleFullscreenMode() {
|
||||||
if (document.fullscreenElement) {
|
if (document.fullscreenElement) {
|
||||||
document.exitFullscreen?.()
|
document.exitFullscreen?.();
|
||||||
isFullscreen.value = false
|
isFullscreen.value = false;
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.requestFullscreen?.()
|
document.documentElement.requestFullscreen?.();
|
||||||
isFullscreen.value = true
|
isFullscreen.value = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Global keyboard shortcuts
|
// Global keyboard shortcuts
|
||||||
function handleKeyPress(event) {
|
function handleKeyPress(event) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case ' ':
|
case " ":
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
togglePlay()
|
togglePlay();
|
||||||
break
|
break;
|
||||||
case 'f':
|
case "f":
|
||||||
case 'F':
|
case "F":
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
// Toggle fullscreen
|
// Toggle fullscreen
|
||||||
break
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('keydown', handleKeyPress)
|
document.addEventListener("keydown", handleKeyPress);
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
// Disable context menu on video and entire page
|
||||||
document.removeEventListener('keydown', handleKeyPress)
|
const disableContextMenu = (e) => {
|
||||||
})
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add contextmenu event listeners
|
||||||
|
document.addEventListener("contextmenu", disableContextMenu);
|
||||||
|
|
||||||
|
// Also disable on video element specifically
|
||||||
|
if (videoElement.value) {
|
||||||
|
videoElement.value.addEventListener("contextmenu", disableContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the handler for cleanup
|
||||||
|
window._disableContextMenu = disableContextMenu;
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener("keydown", handleKeyPress);
|
||||||
|
|
||||||
|
// Clean up context menu listeners
|
||||||
|
if (window._disableContextMenu) {
|
||||||
|
document.removeEventListener("contextmenu", window._disableContextMenu);
|
||||||
|
if (videoElement.value) {
|
||||||
|
videoElement.value.removeEventListener("contextmenu", window._disableContextMenu);
|
||||||
|
}
|
||||||
|
delete window._disableContextMenu;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
6506
src-tauri/gen/schemas/windows-schema.json
Normal file
6506
src-tauri/gen/schemas/windows-schema.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user