暂时无法自动播放
This commit is contained in:
2
API.md
2
API.md
@@ -33,7 +33,7 @@ connect_to_player(config: ConnectionConfig) -> Result<ApiResponse<PlayerState>,
|
||||
**参数:**
|
||||
```json
|
||||
{
|
||||
"host": "192.168.1.100",
|
||||
"host": "127.0.0.1",
|
||||
"port": 8080,
|
||||
"timeout": 10,
|
||||
"autoReconnect": true,
|
||||
|
165
CLAUDE.md
Normal file
165
CLAUDE.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Video Player with Remote Control** - A desktop video player built with Nuxt 4 and Tauri 2 that supports WebSocket-based remote control. The application automatically searches for and plays `video.mp4` files from multiple locations and provides a fullscreen video experience with remote control capabilities.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Tech Stack
|
||||
- **Frontend**: Nuxt 4 (Vue 3) with TypeScript
|
||||
- **Backend**: Rust with Tauri 2 framework
|
||||
- **UI Framework**: NuxtUI v3 with TailwindCSS v4
|
||||
- **Communication**: WebSocket server (port 6666) for remote control
|
||||
- **Package Manager**: pnpm (enforced)
|
||||
|
||||
### Key Components
|
||||
|
||||
#### Frontend (`app/`)
|
||||
- **Main Page**: `app/pages/index.vue` - Fullscreen video player with connection status
|
||||
- **Tauri Module**: `app/modules/tauri.ts` - Auto-imports Tauri APIs
|
||||
- **Assets**: `app/assets/` - CSS and icons
|
||||
- **Layouts**: `app/layouts/` - Application layouts
|
||||
- **Components**: `app/components/` - Reusable UI components
|
||||
|
||||
#### Backend (`src-tauri/`)
|
||||
- **Main Entry**: `src-tauri/src/main.rs` - Application bootstrap
|
||||
- **Library**: `src-tauri/src/lib.rs` - Tauri application setup and state management
|
||||
- **WebSocket Server**: `src-tauri/src/websocket_server.rs` - Remote control server on port 6666
|
||||
- **Player State**: `src-tauri/src/player_state.rs` - Video playback state management
|
||||
- **Commands**: `src-tauri/src/commands.rs` - Tauri commands for frontend interaction
|
||||
- **Permissions**: `src-tauri/capabilities/main.json` - Tauri v2 permission configuration
|
||||
|
||||
### Communication Flow
|
||||
1. **Backend-to-Frontend**: Tauri events (`player-command`, `connection-status`)
|
||||
2. **Frontend-to-Backend**: Tauri commands (`get_player_state`, `load_video`, etc.)
|
||||
3. **Remote Control**: WebSocket server accepts JSON commands for playback control
|
||||
4. **Auto-discovery**: Backend searches for `video.mp4` in multiple directories on startup
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Setup & Installation
|
||||
```bash
|
||||
# Install dependencies (requires Node.js 23+)
|
||||
pnpm install
|
||||
|
||||
# Verify Rust toolchain is installed for Tauri
|
||||
rustc --version # Should be >= 1.70
|
||||
cargo --version
|
||||
```
|
||||
|
||||
### Development
|
||||
```bash
|
||||
# Start development server with Tauri
|
||||
pnpm tauri:dev
|
||||
|
||||
# Frontend only (port 3000)
|
||||
pnpm dev
|
||||
|
||||
# Lint and fix code
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
### Build & Distribution
|
||||
```bash
|
||||
# Build production application
|
||||
pnpm tauri:build
|
||||
|
||||
# Build with debug symbols
|
||||
pnpm tauri:build:debug
|
||||
|
||||
# Generate static site only
|
||||
pnpm generate
|
||||
|
||||
# Version bump
|
||||
pnpm bump
|
||||
```
|
||||
|
||||
### Testing WebSocket Control
|
||||
```bash
|
||||
# Test WebSocket connection (requires Node.js)
|
||||
node test_websocket.js
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### Video Playback
|
||||
- **Auto-loading**: Searches for `video.mp4` in current directory, public folder, and executable path
|
||||
- **Fullscreen**: Auto-fullscreen mode configurable via `app.config.ts`
|
||||
- **Controls**: Play/pause, seek, volume, loop, fullscreen toggle
|
||||
- **Format Support**: MP4 with fallback handling
|
||||
|
||||
### Remote Control
|
||||
- **WebSocket Server**: Runs on port 6666
|
||||
- **JSON Protocol**: Standardized message format for all commands
|
||||
- **Connection Status**: Real-time connection indicator in UI
|
||||
- **Playlist Support**: Load and play from video playlists
|
||||
|
||||
### State Management
|
||||
- **PlayerState**: Comprehensive video state including position, volume, loop status
|
||||
- **VideoInfo**: Metadata tracking for loaded videos
|
||||
- **Real-time Sync**: Frontend/backend state synchronization via Tauri events
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### Application Config
|
||||
- `app.config.ts` - Player settings (volume, fullscreen, etc.)
|
||||
- `player_config.json` - Runtime configuration
|
||||
- `nuxt.config.ts` - Nuxt and Tauri integration settings
|
||||
|
||||
### Build Configuration
|
||||
- `src-tauri/tauri.conf.json` - Tauri application settings
|
||||
- `src-tauri/Cargo.toml` - Rust dependencies and features
|
||||
- `package.json` - Node.js dependencies and scripts
|
||||
|
||||
## Important Notes
|
||||
|
||||
### Permissions
|
||||
- Tauri v2 uses capabilities-based permissions in `src-tauri/capabilities/main.json`
|
||||
- Required permissions: file system access, notifications, OS info, shell commands
|
||||
- WebSocket server runs on port 6666 (configurable in `src-tauri/src/lib.rs`)
|
||||
|
||||
### Development Gotchas
|
||||
- **SSR Disabled**: `ssr: false` in nuxt.config.ts is required for Tauri
|
||||
- **Asset URLs**: Uses `asset://localhost/` protocol for local file access
|
||||
- **Auto-fullscreen**: Configurable but may be disabled in some environments
|
||||
- **Video Discovery**: Searches up to 6 directory levels up from executable
|
||||
|
||||
### File Structure
|
||||
```
|
||||
video-player/
|
||||
├── app/ # Nuxt frontend
|
||||
│ ├── pages/index.vue # Main video player
|
||||
│ ├── modules/tauri.ts # Tauri API auto-imports
|
||||
│ └── assets/ # Static assets
|
||||
├── src-tauri/ # Rust backend
|
||||
│ ├── src/
|
||||
│ │ ├── lib.rs # Main Tauri app
|
||||
│ │ ├── websocket_server.rs # Remote control
|
||||
│ │ └── player_state.rs # Video state
|
||||
│ └── capabilities/ # Tauri permissions
|
||||
├── public/ # Static files (video.mp4)
|
||||
└── types/ # TypeScript definitions
|
||||
```
|
||||
|
||||
### Environment Requirements
|
||||
- **Node.js**: >= 23.0.0
|
||||
- **Rust**: >= 1.70.0
|
||||
- **pnpm**: >= 10.13.1 (enforced)
|
||||
- **Platform**: Windows, macOS, or Linux with Tauri prerequisites
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Adding New Tauri Commands
|
||||
1. Add command function in `src-tauri/src/commands.rs`
|
||||
2. Register command in `src-tauri/src/lib.rs` invoke_handler
|
||||
3. Add TypeScript bindings in frontend
|
||||
4. Update capabilities if new permissions needed
|
||||
|
||||
### WebSocket Protocol
|
||||
- **Connect**: `ws://localhost:6666`
|
||||
- **Commands**: JSON format `{"type":"command","data":{"type":"play"}}`
|
||||
- **Status**: Real-time updates after each command
|
||||
- **Playlist**: Support for multiple video files
|
@@ -9,7 +9,7 @@ export default defineAppConfig({
|
||||
defaultVolume: 50,
|
||||
autoFullscreen: true,
|
||||
showControls: false,
|
||||
webSocketPort: 8080,
|
||||
webSocketPort: 6666,
|
||||
reconnectInterval: 5000
|
||||
},
|
||||
ui: {
|
||||
|
@@ -15,9 +15,12 @@
|
||||
v-if="currentVideo"
|
||||
ref="videoElement"
|
||||
class="w-full h-full object-contain"
|
||||
:src="currentVideo.path"
|
||||
:src="videoSrc"
|
||||
:volume="volume / 100"
|
||||
:loop="isLooping"
|
||||
playsinline
|
||||
:muted="isMuted"
|
||||
preload="auto"
|
||||
autoplay
|
||||
@loadedmetadata="onVideoLoaded"
|
||||
@timeupdate="onTimeUpdate"
|
||||
@@ -75,6 +78,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// remove direct import that breaks vite in dev; we will fallback to file:// URLs
|
||||
const appConfig = useAppConfig()
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
@@ -93,10 +97,46 @@ const volume = ref(appConfig.player.defaultVolume)
|
||||
const isLooping = ref(false)
|
||||
const isFullscreen = ref(false)
|
||||
const showControls = ref(false)
|
||||
const isMuted = ref(true)
|
||||
|
||||
// Video element ref
|
||||
const videoElement = ref(null)
|
||||
|
||||
// Video source URL - reactive ref that gets updated when currentVideo changes
|
||||
const videoSrc = ref('')
|
||||
|
||||
// Convert file path to Tauri-compatible URL
|
||||
async function updateVideoSrc() {
|
||||
const raw = currentVideo.value?.path || ''
|
||||
if (!raw) {
|
||||
console.log('🎥 No video path available')
|
||||
videoSrc.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
const filePath = raw.startsWith('file://') ? raw.slice(7) : raw
|
||||
console.log('🎥 Converting video path:', { raw, filePath })
|
||||
|
||||
// Use file:// protocol for local files in Tauri
|
||||
const absolutePath = filePath.startsWith('/') ? filePath : `/${filePath}`
|
||||
const fileUrl = `file://${absolutePath}`
|
||||
videoSrc.value = fileUrl
|
||||
console.log('🎥 Using file:// URL:')
|
||||
console.log(' - Original path:', raw)
|
||||
console.log(' - File path:', filePath)
|
||||
console.log(' - File URL:', fileUrl)
|
||||
}
|
||||
|
||||
// Watch for changes in currentVideo and update videoSrc
|
||||
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) {
|
||||
@@ -167,9 +207,57 @@ function setVolume(newVolume) {
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,14 +274,22 @@ function onVideoEnded() {
|
||||
}
|
||||
|
||||
function onVideoError(event) {
|
||||
console.error('Video playback error:', 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...')
|
||||
if (appConfig.player.autoFullscreen) {
|
||||
console.log('🔧 Auto fullscreen enabled')
|
||||
// Request fullscreen
|
||||
nextTick(() => {
|
||||
document.documentElement.requestFullscreen?.() ||
|
||||
@@ -202,10 +298,14 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
// Setup Tauri event listeners
|
||||
console.log('🔧 Setting up Tauri listeners...')
|
||||
await setupTauriListeners()
|
||||
|
||||
// Get initial player state
|
||||
console.log('🔧 Getting initial player state...')
|
||||
await updatePlayerState()
|
||||
|
||||
console.log('✅ Component initialization complete')
|
||||
})
|
||||
|
||||
// Setup Tauri event listeners
|
||||
@@ -227,6 +327,7 @@ async function setupTauriListeners() {
|
||||
|
||||
// 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)
|
||||
})
|
||||
@@ -267,6 +368,7 @@ async function updatePlayerState() {
|
||||
|
||||
// Handle player commands from WebSocket
|
||||
async function handlePlayerCommand(command) {
|
||||
console.log('🎮 Received player command:', command)
|
||||
switch (command.type) {
|
||||
case 'play':
|
||||
if (videoElement.value) {
|
||||
@@ -313,25 +415,27 @@ async function handlePlayerCommand(command) {
|
||||
|
||||
// Load video from file path
|
||||
async function loadVideoFromPath(path) {
|
||||
console.log('📥 loadVideoFromPath called with:', path)
|
||||
try {
|
||||
// Convert file path to URL for video element
|
||||
const videoUrl = `file://${path}`
|
||||
const rawPath = path.startsWith('file://') ? path.slice(7) : path
|
||||
console.log('📥 Processing video path:', { originalPath: path, rawPath })
|
||||
|
||||
// Create video info
|
||||
const title = path.split('/').pop() || path
|
||||
const title = rawPath.split('/').pop() || rawPath
|
||||
currentVideo.value = {
|
||||
path: videoUrl,
|
||||
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
|
||||
|
||||
// Update backend state if invoke is available
|
||||
if (invoke) {
|
||||
await invoke('load_video', { path })
|
||||
await invoke('load_video', { path: rawPath })
|
||||
}
|
||||
console.log('📹 Video loaded:', title)
|
||||
console.log('📹 Video loaded successfully:', title)
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to load video:', error)
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"connection": {
|
||||
"controller_host": "127.0.0.1",
|
||||
"controller_port": 8081,
|
||||
"websocket_port": 8080,
|
||||
"websocket_port": 6666,
|
||||
"auto_connect": true,
|
||||
"reconnect_interval": 5000,
|
||||
"connection_timeout": 10000
|
||||
|
BIN
public/video.mp4
Normal file
BIN
public/video.mp4
Normal file
Binary file not shown.
@@ -39,6 +39,10 @@
|
||||
"os:allow-locale",
|
||||
"fs:allow-document-read",
|
||||
"fs:allow-document-write",
|
||||
"fs:default",
|
||||
"fs:allow-read-dir",
|
||||
"fs:allow-read-file",
|
||||
"fs:allow-stat",
|
||||
"store:default",
|
||||
"core:webview:allow-create-webview",
|
||||
"core:webview:allow-create-webview-window"
|
||||
|
@@ -1 +1 @@
|
||||
{"main":{"identifier":"main","description":"Capabilities for the app window","local":true,"windows":["main","secondary"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","shell:allow-open",{"identifier":"shell:allow-execute","allow":[{"args":["-c",{"validator":"\\S+"}],"cmd":"sh","name":"exec-sh","sidecar":false}]},"notification:default","os:allow-platform","os:allow-arch","os:allow-family","os:allow-version","os:allow-locale","fs:allow-document-read","fs:allow-document-write","store:default","core:webview:allow-create-webview","core:webview:allow-create-webview-window"]}}
|
||||
{"main":{"identifier":"main","description":"Capabilities for the app window","local":true,"windows":["main","secondary"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","shell:allow-open",{"identifier":"shell:allow-execute","allow":[{"args":["-c",{"validator":"\\S+"}],"cmd":"sh","name":"exec-sh","sidecar":false}]},"notification:default","os:allow-platform","os:allow-arch","os:allow-family","os:allow-version","os:allow-locale","fs:allow-document-read","fs:allow-document-write","fs:default","fs:allow-read-dir","fs:allow-read-file","fs:allow-stat","store:default","core:webview:allow-create-webview","core:webview:allow-create-webview-window"]}}
|
@@ -7,11 +7,12 @@ mod commands;
|
||||
use tauri::{
|
||||
menu::{Menu, MenuItem},
|
||||
tray::TrayIconBuilder,
|
||||
Manager
|
||||
Manager,
|
||||
Emitter
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use std::sync::Arc;
|
||||
use player_state::PlayerState;
|
||||
use player_state::{PlayerState, VideoInfo};
|
||||
use websocket_server::WebSocketServer;
|
||||
|
||||
pub fn run() {
|
||||
@@ -22,7 +23,7 @@ pub fn run() {
|
||||
app.manage(player_state.clone());
|
||||
|
||||
// Start WebSocket server in background using tauri's async runtime
|
||||
let ws_server = WebSocketServer::new(8080, player_state.clone());
|
||||
let ws_server = WebSocketServer::new(6666, player_state.clone());
|
||||
let app_handle = app.handle().clone();
|
||||
|
||||
// Use tauri's runtime handle to spawn the async task
|
||||
@@ -32,6 +33,93 @@ pub fn run() {
|
||||
}
|
||||
});
|
||||
|
||||
// Try to auto-load video.mp4 from common locations (cwd, public/, executable dir)
|
||||
let app_handle_load = app.handle().clone();
|
||||
let player_state_for_load = player_state.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mut candidates: Vec<std::path::PathBuf> = Vec::new();
|
||||
if let Ok(cwd) = std::env::current_dir() {
|
||||
log::info!("🔍 Searching in current working directory: {}", cwd.display());
|
||||
candidates.push(cwd.join("video.mp4"));
|
||||
candidates.push(cwd.join("public").join("video.mp4"));
|
||||
}
|
||||
if let Ok(exe_path) = std::env::current_exe() {
|
||||
log::info!("📁 Executable path: {}", exe_path.display());
|
||||
let mut ancestor_opt = exe_path.parent();
|
||||
let mut steps = 0;
|
||||
while let Some(dir) = ancestor_opt {
|
||||
log::info!("🔍 Searching in ancestor directory: {}", dir.display());
|
||||
candidates.push(dir.join("video.mp4"));
|
||||
candidates.push(dir.join("public").join("video.mp4"));
|
||||
steps += 1;
|
||||
if steps > 6 { break; }
|
||||
ancestor_opt = dir.parent();
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("📋 Total search candidates: {}", candidates.len());
|
||||
for (i, candidate) in candidates.iter().enumerate() {
|
||||
log::info!(" {}. {}", i + 1, candidate.display());
|
||||
}
|
||||
|
||||
for candidate in candidates {
|
||||
if tokio::fs::metadata(&candidate).await.is_ok() {
|
||||
let path_string = candidate.to_string_lossy().to_string();
|
||||
log::info!("✅ Found video file: {}", path_string);
|
||||
// Update backend player state
|
||||
{
|
||||
let mut state = player_state_for_load.lock().await;
|
||||
let title = candidate.file_name().and_then(|n| n.to_str()).unwrap_or("video.mp4").to_string();
|
||||
let video_info = VideoInfo {
|
||||
path: path_string.clone(),
|
||||
title,
|
||||
duration: None,
|
||||
size: None,
|
||||
format: None,
|
||||
};
|
||||
state.load_video(video_info);
|
||||
// 默认启用循环并开始播放
|
||||
state.set_loop(true);
|
||||
state.play();
|
||||
}
|
||||
|
||||
// Notify frontend to load and play the video (use absolute path; frontend will add file:// if needed)
|
||||
let _ = app_handle_load.emit("player-command", serde_json::json!({
|
||||
"type": "loadVideo",
|
||||
"path": path_string
|
||||
}));
|
||||
let _ = app_handle_load.emit("player-command", serde_json::json!({
|
||||
"type": "setLoop",
|
||||
"enabled": true
|
||||
}));
|
||||
let _ = app_handle_load.emit("player-command", serde_json::json!({
|
||||
"type": "play"
|
||||
}));
|
||||
|
||||
// Re-emit after a short delay to ensure frontend listeners are ready in dev mode
|
||||
let app_handle_clone = app_handle_load.clone();
|
||||
let delayed_path = path_string.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(800)).await;
|
||||
let _ = app_handle_clone.emit("player-command", serde_json::json!({
|
||||
"type": "loadVideo",
|
||||
"path": delayed_path
|
||||
}));
|
||||
let _ = app_handle_clone.emit("player-command", serde_json::json!({
|
||||
"type": "setLoop",
|
||||
"enabled": true
|
||||
}));
|
||||
let _ = app_handle_clone.emit("player-command", serde_json::json!({
|
||||
"type": "play"
|
||||
}));
|
||||
});
|
||||
return; // Exit early since we found a video
|
||||
}
|
||||
}
|
||||
|
||||
log::warn!("❌ No video.mp4 file found in any search location");
|
||||
});
|
||||
|
||||
// Setup tray
|
||||
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
||||
let menu = Menu::with_items(app, &[&quit_i])?;
|
||||
@@ -50,7 +138,7 @@ pub fn run() {
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
log::info!("Video Player initialized, WebSocket server starting on port 8080");
|
||||
log::info!("Video Player initialized, WebSocket server starting on port 6666");
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
|
@@ -1,6 +1,11 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use env_logger::Env;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let env = Env::default().default_filter_or("info");
|
||||
let _ = env_logger::Builder::from_env(env)
|
||||
.format_timestamp_millis()
|
||||
.try_init();
|
||||
video_player_lib::run();
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ impl WebSocketServer {
|
||||
}
|
||||
|
||||
pub async fn start(&self, app_handle: AppHandle) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = format!("127.0.0.1:{}", self.port);
|
||||
let addr = format!("0.0.0.0:{}", self.port);
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
log::info!("WebSocket server listening on: {}", addr);
|
||||
@@ -67,6 +67,23 @@ async fn handle_connection(
|
||||
let status_update = StatusUpdate::new(state.clone());
|
||||
let message = serde_json::to_string(&status_update)?;
|
||||
ws_sender.send(Message::Text(message)).await?;
|
||||
if let Some(video) = &state.current_video {
|
||||
let load_msg = serde_json::json!({
|
||||
"type": "command",
|
||||
"data": { "type": "loadVideo", "path": video.path }
|
||||
});
|
||||
ws_sender.send(Message::Text(load_msg.to_string())).await.ok();
|
||||
let loop_msg = serde_json::json!({
|
||||
"type": "command",
|
||||
"data": { "type": "setLoop", "enabled": state.is_looping }
|
||||
});
|
||||
ws_sender.send(Message::Text(loop_msg.to_string())).await.ok();
|
||||
let play_msg = serde_json::json!({
|
||||
"type": "command",
|
||||
"data": { "type": "play" }
|
||||
});
|
||||
ws_sender.send(Message::Text(play_msg.to_string())).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
// Emit connection status to frontend
|
||||
@@ -85,6 +102,12 @@ async fn handle_connection(
|
||||
if ws_message.msg_type == "command" {
|
||||
if let Ok(command) = serde_json::from_value::<PlaybackCommand>(ws_message.data) {
|
||||
handle_playback_command(command, &player_state, &app_handle).await?;
|
||||
|
||||
// Send updated status to client after handling command
|
||||
let state = player_state.lock().await;
|
||||
let status_update = StatusUpdate::new(state.clone());
|
||||
let message = serde_json::to_string(&status_update)?;
|
||||
ws_sender.send(Message::Text(message)).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
// Simple WebSocket client to test our player
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://127.0.0.1:8080');
|
||||
const ws = new WebSocket('ws://127.0.0.1:6666');
|
||||
|
||||
ws.on('open', function open() {
|
||||
console.log('✅ Connected to video player WebSocket server');
|
||||
|
Reference in New Issue
Block a user