Windows下Rust編碼實現MP4點播服務器

Rust編碼可以實現眾多簡潔、可靠、高效的應用,但語法邏輯要求嚴格,尤其是依賴庫的選擇調用,需要耐心堅持“推敲”。借助DeepSeek并反復編程調試和問答改進,可以最終得到完整有效的Rust編碼。下面分享Windows下Rust編碼實現MP4點播服務器設計實現。

1 整體規劃設計

1.1 功能分析設計

簡單MP4點播服務器,設計簡單html頁面進行播放文件的選擇和播放控制。

Rust后端編碼設計,采用簡易mp4庫,不選用FFmpeg,避免復雜的環境配置和開發過程中的過多“坑”險。

項目整體規劃如下:

  • Rust后端:處理MP4文件讀取和HTTP服務
  • 前端HTML頁面:遠程選擇播放文件和控制播放
  • 輕量級架構:使用高效Rust庫實現核心功能
  • 文中文件名稱的前后端支持

1.2 初始項目構建

把設想和功能要求,拋給AI-LLM工具,如騰迅的ima,借助內置的Hunyuan/DeepSeek,獲取初始的項目構造和必須的文件編碼,減輕初步完全編碼的困難。以此為參照,開始后續項目架構和編碼設計。關鍵的提問描述如下:

請為初學者給出Windows下Rust編碼的簡單MP4點播服務器,設計簡單html頁面進行遠程選擇播放文件并進行播放控制。不用FFMPEG。給出項目文件結構和完整編碼。

圖1是騰迅ima的運用組合截圖。

圖1 騰迅ima項目借力的運用組合截圖

2 項目構建準備

2.1 項目整體構建

適應Windows操作系統,選擇MinGW-GNU支持的Rust底層支持環境。簡化簡潔安裝,避免過多且易于沖突調整,這里采用離線版:rust-1.88.0-x86_64-pc-windows-gnu.msi,一步到位。

進而安裝采用RustRoverIDE集成開發環境,執照上述ima的設計指導,構建整個項目工程,框架結構如下文本框所示。

mp4-server/

├── Cargo.toml??????????? # 項目依賴配置

├── videos/?????????????? # 存放MP4視頻文件的目錄

├── static/?????????????? ?# 前端靜態文件

│?? └── index.html??????? ?????# 交互操控界面

└── src/

??? ├── main.rs?????????? ?????# 服務器入口

??? ├── handlers.rs?????? ??????# HTTP請求處理

??? └── mp4_utils.rs????? ??????# MP4文件處理工具

2.2 項目依賴管理

添加項目所需依賴--Cargo.toml依賴配置

[package]name = "mp4_server2"version = "0.1.0"edition = "2024"[dependencies]warp = "0.3"????????????? ?????????????????????# HTTP服務器框架

tokio = { version = "1", features = ["full"] } ?????????# 異步運行時

serde = { version = "1.0", features = ["derive"] } ?????# 序列化

serde_json = "1.0"??????? ??????????????????????# JSON處理

mp4 = "0.14.0"??????????? ?????????????????????# MP4文件處理庫

futures = "0.3"?????????? ???????????????????????# 異步支持

lazy_static = "1.4"regex = "1.11.1"?????? ?????????????????????????# 靜態初始化

encoding = "0.2.33"percent-encoding = "2.3.1"tokio-util = "0.7.16"hyper = "0.14.32"? ?????????????????????????????# 支持GBK/GB18030解碼

3 后端編碼實現

3.1 Mp4文件處理

src/mp4_utils.rs

use mp4::{Mp4Reader, Result}; use std::fs::File;

use std::io::{BufReader, Read, Seek, SeekFrom}; use std::path::Path;

// 獲取視頻文件元數據

pub fn get_video_metadata(path: &Path) -> Result<(u64, u32, u32)> {

??? let file = File::open(path)?;

??? let size = file.metadata()?.len();

??? let reader = BufReader::new(file);

??? let mp4 = Mp4Reader::read_header(reader, size)?;

??? let duration = mp4.duration().as_secs();

??? let width = mp4.tracks().values().next().map(|t| t.width()).unwrap_or(0);

??? let height = mp4.tracks().values().next().map(|t| t.height()).unwrap_or(0);

??? Ok((duration, width as u32, height as u32))

}

// 流式傳輸視頻文件

pub fn stream_video_file(path: &Path, start: u64, end: Option<u64>) -> std::io::Result<Vec<u8>> {

??? let mut file = File::open(path)?;

??? let file_size = file.metadata()?.len();

??? // 計算實際讀取范圍

??? let end = end.unwrap_or(file_size); let length = end - start;

??? // 定位并讀取數據

??? file.seek(SeekFrom::Start(start))?;

??? let mut buffer = vec![0; length as usize];

??? file.read_exact(&mut buffer)?;

??? Ok(buffer)

}

3.2 Http請求處理

src/handlers.rs

use warp::{Rejection, Reply};

use std::path::{PathBuf}; use std::fs; use std::ffi::OsString;

use encoding::{all::GB18030, EncoderTrap, Encoding};

use percent_encoding::{percent_decode, utf8_percent_encode, NON_ALPHANUMERIC};

use serde::Serialize; use tokio_util::io::ReaderStream; use hyper::Body; use crate::mp4_utils;

#[derive(Serialize)]

struct VideoInfo { name: String, duration: u64, width: u32, height: u32, }

// 獲取視頻列表(兼容中文文件名)

pub async fn list_videos() -> Result<impl Reply, Rejection> {

??? let videos_dir = PathBuf::from("./videos"); let mut videos = Vec::new();

??? if let Ok(entries) = fs::read_dir(videos_dir) {

??????? for entry in entries.flatten() {

??????????? let path = entry.path();

??????????? if path.is_file() && path.extension().map_or(false, |ext| ext == "mp4") {

??????????????? // 使用OsString原生處理文件名(兼容GBK/UTF-8

??????????????? let os_name: OsString = entry.file_name();

??????????????? let name = os_name.to_string_lossy().into_owned(); // 轉換為UTF-8字符串

??????????????? if let Ok((duration, width, height)) = mp4_utils::get_video_metadata(&path) {

??????????????????? videos.push(VideoInfo { name, duration, width, height });

??????????????? }

??????????? }

??????? }

??? }

??? Ok(warp::reply::json(&videos))

}

// 流式傳輸視頻

pub async fn stream_video(name: String, range: Option<String>) -> Result<impl Reply, Rejection> {

??? // 1. URL解碼文件名(前端encodeURIComponent的逆操作)

??? let decoded_name = percent_decode(name.as_bytes())

??????? .decode_utf8().map_err(|_| warp::reject::not_found())?

??????? .into_owned();

??? // 2. UTF-8轉換為GBKWindows原生編碼)

??? let _gbk_bytes = GB18030.encode(&decoded_name, EncoderTrap::Strict)

??????? .map_err(|_| warp::reject::not_found())?;

??? // 3. 通過OsString構建路徑(關鍵修復)

??? // 使用OsString::from而不是from_vec

??? let os_name = OsString::from(decoded_name.clone());

??? let path = PathBuf::from("./videos").join(os_name);

??? // 4. 解析Range

??? let (start, end) = if let Some(range_header) = range {

??????? parse_range_header(&range_header, &path)?

??? } else {

??????? (0, None)

??? };

??? // 5. 異步讀取文件

??? let file = tokio::fs::File::open(&path).await

??????? .map_err(|_| warp::reject::not_found())?;

??? // 6. 創建流式響應體

??? let stream = ReaderStream::new(file); let body = Body::wrap_stream(stream);

??? // 7. 獲取文件元數據

??? let metadata = tokio::fs::metadata(&path).await.map_err(|_| warp::reject::not_found())?;

??? let file_size = metadata.len();

??? // 8. 構建Content-Range

??? let content_range = if let Some(end) = end {

??????? let end = end.min(file_size - 1);

??????? format!("bytes {}-{}/{}", start, end, file_size)

??? } else {

??????? format!("bytes {}-{}/{}", start, file_size - 1, file_size)

??? };

??? // 9. 設置響應頭(解決瀏覽器中文亂碼)

??? let safe_name = utf8_percent_encode(&decoded_name, NON_ALPHANUMERIC).to_string();

??? let content_disposition = format!(

??????? "attachment; filename=\"{}\"; filename*=utf-8''{}", safe_name, safe_name );

??? // 10. 構建響應,使用warp::reply::Response包裝Body

??? let mut response = warp::reply::Response::new(body);

??? let headers = response.headers_mut();

??? headers.insert("Content-Type", "video/mp4".parse().unwrap());

??? headers.insert("Content-Range", content_range.parse().unwrap());

??? headers.insert("Content-Disposition", content_disposition.parse().unwrap());

??? Ok(response)

}

// 解析Range

fn parse_range_header(range: &str, path: &PathBuf) -> Result<(u64, Option<u64>), Rejection> {

??? let file_size = fs::metadata(path).map_err(|_| warp::reject::not_found())?.len();

??? if let Some(captures) = regex::Regex::new(r"bytes=(\d+)-(\d*)").unwrap()captures(range) {

??????? let start = captures.get(1).unwrap().as_str().parse::<u64>()

??????????? .map_err(|_| warp::reject::not_found())?;

??????? let end = captures.get(2).unwrap().as_str();

??????? let end = if end.is_empty() {

??????????? None

??????? } else {

??????????? Some(end.parse::<u64>().map_err(|_| warp::reject::not_found())?.min(file_size - 1))

??????? };

??????? return Ok((start, end));

??? }

??? Err(warp::reject::not_found())

}

// 輔助函數:URL編碼轉換(供前端使用)

pub fn encode_filename(name: &str) -> String {

??? utf8_percent_encode(name, NON_ALPHANUMERIC).to_string()

}

3.3 服務器主入口

src/main.rs

mod handlers; mod mp4_utils;

use warp::Filter; use handlers::{list_videos, stream_video};

#[tokio::main]

async fn main() {

??? // 創建路由

??? let list_route = warp::path!("api" / "videos").and(warp::get()).and_then(list_videos);

??? let stream_route = warp::path!("api" / "videos" / String)

??????? .and(warp::get()).and(warp::header::optional("Range")).and_then(stream_video);

??? let static_files = warp::path("static").and(warp::fs::dir("./static"));

??? let index = warp::path::end().and(warp::fs::file("./static/index.html"));

??? // 組合所有路由

??? let routes = list_route.or(stream_route).or(static_files).or(index)

??????? .with(warp::cors().allow_any_origin());

??? // 啟動服務器

??? println!("Server started at http://localhost:9700");

??? warp::serve(routes).run(([0, 0, 0, 0], 9700)).await;

}

4 前端交互頁面設計

static/index.html

<!DOCTYPE html>

<html lang="en">

<head>

? <meta charset="UTF-8">

? <meta name="viewport" content="width=device-width, initial-scale=1.0">

? <title>MP4視頻服務器</title>

? <style>

??? :root { --primary-color: #3498db; --secondary-color: #2980b9;

????? --background-color: #f5f7fa; --card-bg: #ffffff; --text-color: #333333;

????? --border-color: #e0e0e0; --success-color: #2ecc71; --hover-color: #f1f9ff; }

??? * { margin: 0; padding: 0; box-sizing: border-box;

????? font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }

??? body { background-color: var(--background-color);

????? color: var(--text-color); line-height: 1.6; padding: 2px; }

??? .container { max-width: 1200px; margin: 0 auto; display: grid;

????? grid-template-columns: 1fr 2fr; gap: 10px; }

??? @media (max-width: 768px) { .container { grid-template-columns: 1fr; } }

??? header { grid-column: 1 / -1; text-align: center; margin-bottom: 2px;

????? padding: 20px 0; border-bottom: 1px solid var(--border-color); }

??? h1 { color: var(--primary-color); font-size: 2.5rem; margin-bottom: 1px; }

??? .card { background: var(--card-bg); border-radius: 10px; padding: 25px;

????? box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); transition: transform 0.3s ease; }

??? .card:hover { transform: translateY(-5px); }

??? .video-list-container { height: 100%; display: flex; flex-direction: column; }

?? ?.video-list-header { display: flex; justify-content: space-between;

????? align-items: center; margin-bottom: 15px; padding-bottom: 10px;

????? border-bottom: 1px solid var(--border-color); }

??? .video-list-header h2 { font-size: 1.5rem; color: var(--primary-color); }

??? .video-count { background: var(--primary-color); color: white;

????? border-radius: 20px; padding: 2px 10px; font-size: 0.9rem; }

??? #videoList { list-style: none; max-height: 500px; overflow-y: auto;

????? flex-grow: 1; border: 1px solid var(--border-color);

????? border-radius: 8px; padding: 5px; }

??? #videoList::-webkit-scrollbar { width: 8px; }

??? #videoList::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }

??? #videoList::-webkit-scrollbar-thumb { background: var(--primary-color); border-radius: 4px; }

??? #videoList li { padding: 12px 15px; border-bottom: 1px solid var(--border-color);

????? cursor: pointer; transition: background-color 0.2s; display: flex;

????? justify-content: space-between; align-items: center; }

??? #videoList li:last-child { border-bottom: none; }

??? #videoList li:hover { background-color: var(--hover-color); }

??? #videoList li.active { background-color: var(--primary-color); color: white; }

??? .video-duration { background: var(--secondary-color); color: white;

????? border-radius: 4px; padding: 2px 8px; font-size: 0.85rem; }

??? .player-container { display: flex; flex-direction: column; gap: 20px; }

??? .video-player-wrapper { position: relative; padding-bottom: 56.25%; /* 16:9 aspect ratio */

????? height: 0; overflow: hidden; border-radius: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); }

??? #videoPlayer { position: absolute; top: 0; left: 0; width: 100%; height: 100%;background: #000; }

??? .controls { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; background:var(--card-bg);

????? padding: 15px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }

??? .btn { background: var(--primary-color); color: white; border: none; padding: 10px 15px;

????? border-radius: 5px; cursor: pointer; transition: background 0.3s;

????? font-weight: 600; display: flex; align-items: center; gap: 5px; }

??? .btn:hover { background: var(--secondary-color); }

??? .btn i { font-size: 1.2rem; }

??? .seek-container { flex-grow: 1; display: flex; align-items: center; gap: 10px;

??? #seekBar { flex-grow: 1; height: 8px; -webkit-appearance: none;

????? background: #e0e0e0; border-radius: 4px; outline: none; }

??? #seekBar::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px;

????? background: var(--primary-color); border-radius: 50%; cursor: pointer; }

??? .time-display { font-size: 0.9rem; color: #666; min-width: 80px; text-align: center; }

??? .search-container { margin-top: 15px; position: relative; }

??? #searchInput { width: 100%; padding: 10px 15px 10px 40px;

????? border: 1px solid var(--border-color); border-radius: 5px; font-size: 1rem; }

??? .search-icon { position: absolute; left: 15px; top: 50%; transform: translateY(-50%); color: #777; }

??? .empty-state { text-align: center; padding: 30px; color: #777; }

??? .empty-state i { font-size: 3rem; margin-bottom: 15px; color: #ddd; }

??? .video-info { display: flex; justify-content: space-between;

????? font-size: 0.9rem; color: #666; margin-top: 5px; }

? </style>

? <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

</head>

<body>

<div class="container">

? <header>

??? <h1><i class="fas fa-play-circle"></i>MP4視頻服務器</h1>

??? <p>流式傳輸管理MP4視頻流,愷肇乾,2025-08-06</p>

? </header>

? <div class="video-list-container card">

??? <div class="video-list-header">

????? <h2>有效視頻數</h2>

????? <span class="video-count">12 videos</span>

??? </div>

??? <div class="search-container">

????? <i class="fas fa-search search-icon"></i>

????? <input type="text" id="searchInput" placeholder="搜索視頻...">

??? </div>

??? <ul id="videoList">

????? <!-- Videos will be populated by JavaScript -->

????? <li>

??????? <span>Introduction to Rust Programming</span>

??????? <span class="video-duration">10:24</span>

??? ??</li>

????? <li class="active">

??????? <span>Building Web Servers with Rust</span>

??????? <span class="video-duration">15:42</span>

????? </li>

????? <li>

??????? <span>Concurrency in Rust Explained</span>

??????? <span class="video-duration">22:18</span>

????? </li>

????? <li>

??????? <span>Rust vs C++ Performance Comparison</span>

??????? <span class="video-duration">18:05</span>

????? </li>

????? <li>

??????? <span>Memory Management in Rust</span>

??????? <span class="video-duration">12:37</span>

????? </li>

????? <li>

??????? <span>Creating CLI Tools with Rust</span>

??????? <span class="video-duration">14:56</span>

????? </li>

????? <li>

??????? <span>WebAssembly with Rust Tutorial</span>

??????? <span class="video-duration">25:12</span>

????? </li>

????? <li>

??????? <span>Async Programming in Rust</span>

??????? <span class="video-duration">20:45</span>

????? </li>

????? <li>

??????? <span>Rust for Embedded Systems</span>

??????? <span class="video-duration">16:33</span>

????? </li>

????? <li>

??????? <span>Testing Strategies in Rust</span>

??????? <span class="video-duration">11:29</span>

????? </li>

????? <li>

??????? <span>Building REST APIs with Actix</span>

??????? <span class="video-duration">19:17</span>

????? </li>

????? <li>

??????? <span>Rust Macros Deep Dive</span>

??????? <span class="video-duration">17:48</span>

????? </li>

??? </ul>

? </div>

? <div class="player-container">

??? <div class="card">

????? <div class="video-player-wrapper">

??????? <video id="videoPlayer" controls poster="https://picsum.photos/800/450?random">

????????? <source src="" type="video/mp4">Your browser does not support the video tag.

??????? </video>

????? </div>

??? </div>

??? <div class="controls card">

????? <button id="playBtn" class="btn"><i class="fas fa-play"></i>播放</button>

????? <button id="pauseBtn" class="btn"><i class="fas fa-pause"></i>暫停</button>

????? <div class="seek-container">

??????? <span class="time-display" id="currentTime">0:00</span>

??????? <input type="range" id="seekBar" min="0" max="100" value="0">

??????? <span class="time-display" id="duration">0:00</span>

????? </div>

????? <button id="fullscreenBtn" class="btn"><i class="fas fa-expand"></i></button>

??? </div>

??? <div class="card">

????? <h3>現在正在播放: <span id="nowPlaying">Building Web Servers with Rust</span></h3>

????? <div class="video-info">

??????? <span>時長: <span id="videoDuration">15:42</span></span>

??????? <span>分辯率: 1080p</span>

??????? <span>大小: 未定 MB</span>

????? </div>

??? </div>

? </div>

</div>

</body>

<script>

? // 獲取視頻列表

? async function loadVideos() {

??? try {

????? const response = await fetch('/api/videos');

????? const videos = await response.json();

?? ???const listElement = document.getElementById('videoList');

????? const videoCount = document.querySelector('.video-count');

????? // 清空列表(除了示例項)

????? listElement.innerHTML = '';

????? if (videos.length === 0) {

??????? listElement.innerHTML = `

?????? ?????<div class="empty-state">

????????????? <i class="fas fa-film"></i>

????????????? <p>No videos found</p>

????????????? <p>Upload videos to the server to get started</p>

??????????? </div>

????????? `;

??????? videoCount.textContent = '0 ';

??????? return;

????? }

????? videoCount.textContent = `${videos.length} ${videos.length === 1 ? '' : ''}`;

????? videos.forEach(video => {

??????? const li = document.createElement('li');

??????? // 格式化時間為 MM:SS

??????? const minutes = Math.floor(video.duration / 60);

??????? const seconds = video.duration % 60;

??????? const formattedTime = `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;

??????? li.innerHTML = `

??????????? <span>${video.name}</span>

??????????? <span class="video-duration">${formattedTime}</span>

????????? `;

??????? li.onclick = () => {

????????? // 移除所有active

????????? document.querySelectorAll('#videoList li').forEach(item => {

??????????? item.classList.remove('active');

????????? });

????? ????// 添加active類到當前項

????????? li.classList.add('active');

????????? playVideo(video.name);

??????? };

??????? listElement.appendChild(li);

????? });

????? // 默認選擇第一個視頻

????? if (videos.length > 0) {

??????? listElement.firstChild.classList.add('active');

??????? playVideo(videos[0].name);

????? }

??? } catch (error) {

????? console.error('Error loading videos:', error);

????? const listElement = document.getElementById('videoList');

????? listElement.innerHTML = `

????????? <div class="empty-state">

??????????? <i class="fas fa-exclamation-triangle"></i>

??????????? <p>Failed to load videos</p>

??????????? <p>Please check your connection</p>

????????? </div>

??????? `;

??? }

? }

? // 播放視頻

? function playVideo(name) {

??? const player = document.getElementById('videoPlayer');

??? const nowPlaying = document.getElementById('nowPlaying');

??? const encodedName = encodeURIComponent(name);

??? nowPlaying.textContent = name;

??? player.src = `/api/videos/${encodedName}`;

??? player.load();

? ??// 更新視頻信息

??? updateVideoInfo(name);

? }

? // 更新視頻信息(模擬)

? function updateVideoInfo(name) {

??? const durationElement = document.getElementById('videoDuration');

??? const videoItems = document.querySelectorAll('#videoList li');

??? // 在實際應用中,這里會從API獲取詳細信息

??? // 這里只是模擬從列表項中獲取時長

??? videoItems.forEach(item => {

????? if (item.textContent.includes(name)) {

??????? const durationSpan = item.querySelector('.video-duration');

??????? if (durationSpan) {

????????? durationElement.textContent = durationSpan.textContent;

??????? }

????? }

??? });

? }

? // 搜索功能

? function setupSearch() {

??? const searchInput = document.getElementById('searchInput');

??? searchInput.addEventListener('input', function() {

????? const searchTerm = this.value.toLowerCase();

????? const videoItems = document.querySelectorAll('#videoList li');

????? videoItems.forEach(item => {

??????? const videoName = item.querySelector('span:first-child').textContent.toLowerCase();

??????? if (videoName.includes(searchTerm)) {

????????? item.style.display = 'flex';

??????? } else {

????????? item.style.display = 'none';

??????? }

????? });

??? });

? }

? // 播放控制

? function setupPlayerControls() {

??? const player = document.getElementById('videoPlayer');

??? const playBtn = document.getElementById('playBtn');

??? const pauseBtn = document.getElementById('pauseBtn');

??? const seekBar = document.getElementById('seekBar');

??? const currentTime = document.getElementById('currentTime');

??? const duration = document.getElementById('duration');

??? const fullscreenBtn = document.getElementById('fullscreenBtn');

??? playBtn.addEventListener('click', () => {

????? player.play();

??? });

??? pauseBtn.addEventListener('click', () => {

????? player.pause();

??? });

??? // 更新進度條和時間顯示

??? player.addEventListener('timeupdate', () => {

????? const value = (player.currentTime / player.duration) * 100;

????? seekBar.value = isNaN(value) ? 0 : value;

????? // 更新時間顯示

????? currentTime.textContent = formatTime(player.currentTime);

??? });

??? // 視頻加載后更新總時長

??? player.addEventListener('loadedmetadata', () => {

????? duration.textContent = formatTime(player.duration);

??? });

??? seekBar.addEventListener('input', () => {

????? const time = player.duration * (seekBar.value / 100);

????? player.currentTime = time;

??? });

??? // 全屏功能

??? fullscreenBtn.addEventListener('click', () => {

????? if (player.requestFullscreen) {

??????? player.requestFullscreen();

????? } else if (player.mozRequestFullScreen) {

??????? player.mozRequestFullScreen();

????? } else if (player.webkitRequestFullscreen) {

??????? player.webkitRequestFullscreen();

????? } else if (player.msRequestFullscreen) {

??????? player.msRequestFullscreen();

????? }

??? });

? }

? // 格式化時間為 MM:SS

? function formatTime(seconds) {

??? const minutes = Math.floor(seconds / 60);

??? const secs = Math.floor(seconds % 60);

??? return `${minutes}:${secs < 10 ? '0' : ''}${secs}`;

? }

? // 初始化

? window.onload = function() {

??? loadVideos();

??? setupPlayerControls();

??? setupSearch();

? };

</script>

</html>

5 測調試部署運行

5.1 使用說明

  1. 創建項目結構并添加上述文件
  2. 在項目根目錄創建videos文件夾并放入MP4文件
  3. 運行服務器:cargo run
  4. 訪問 http://localhost:9700

圖2 瀏覽器交互運行及其跟蹤調試組合窗口截圖

5.2 IDE跟蹤調試

RustRoverIDE跟蹤調試組合窗口截圖,如圖3所示。

圖3 RustRoverIDE跟蹤調試組合窗口截圖

5.3 功能特點

  1. MP4文件處理:使用mp4-rust庫高效讀取MP4文件
  2. 流式傳輸:支持HTTP Range請求,實現視頻流式播放
  3. 元數據提取:自動獲取視頻時長和分辨率信息
  4. 響應式界面:簡潔的前端界面支持視頻選擇和播放控制
  5. 輕量級架構:無外部依賴,僅使用Rust標準庫和高效組件

5.4 部署運行

將生成的exe文件連同static、videos兩個目錄文件,一起放到服務器上,進行exe執行文件,即可遠程通過瀏覽器交互訪問了。

注意,生成exe文件時,主程序中的IP需是:“0,0,0,0”,不能再是“127.0.0.1”。

??? warp::serve(routes).run(([0, 0, 0, 0], 9700)).await;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/94625.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/94625.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/94625.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ubuntu-相關指令

1、串口1.1確認在系統中檢查設備是否正常加載&#xff0c;在終端輸入以下命令&#xff1a;way1&#xff1a;ll /dev | grep ttyUSB&#xff08;ll是LL的小寫&#xff09; way2&#xff1a;ll /dev | grep ttyACM way3&#xff1a;ll /dev | grep ttyCH343USB&#…

docker容器臨時文件去除,服務器容量空間

概述&#xff1a; 接到告警提醒&#xff0c;服務器容量不足&#xff0c;去查看了一下&#xff0c;發現確實100g左右容量已基本用完&#xff1b;分析&#xff1a; 1&#xff09;查看根目錄下哪些文件夾占用容量較大 使用命令“ du -ah --max-depth1 / ” 查看目標目錄下所有文件…

損耗對信號質量的影響

損耗通常分為介質損耗與導體損耗&#xff1a;介質損耗&#xff1a;介質被施加電場后介質內部帶電粒子在外加電場的作用力下進行微小移動介質損耗與頻率成正比導體損耗&#xff1a;導體由于存在電阻&#xff0c;在有電流流過時產生的熱量造成的損耗為導體損耗。同時&#xff0c;…

【42】【OpenCV C++】 計算圖像某一列像素方差 或 某一行像素的方差;

文章目錄1 要使用到的函數 和 原理1.1 cv::meanStdDev 函數詳解——計算均值和標準差1 .2 方差的通俗解釋2 代碼實現3 問題3.1 入口參數const cv::Mat& img 和 const cv::Mat img區別項目要求&#xff1a;C OPenCV 中 圖像img ,當 string ROIdirection “H”時&#xff0c;…

元圖 CAD 插件化革命:突破效率瓶頸,重構智能協作新范式

在建筑、機械、機電等工程領域&#xff0c;傳統CAD軟件的功能固化與場景割裂已成為效率提升的瓶頸。設計師常面臨“通用工具難適配專業需求”、“跨平臺協作效率低下”、“數據孤島阻礙創新”等痛點。元圖CAD憑借“場景插件化“核心技術&#xff0c;以模塊化能力突破行業桎梏&a…

T:歸并排序

歸并排序.逆序對簡介.歸并排序.習題.逆序對簡介 \;\;\;\;\;\;\;\;簡單介紹一下歸并排序的原理&#xff0c;逆序對的基本概念&#xff0c;然后收集相關的練習。 直接用一個基礎問題來引入。 因此知道了: \;\;\;\;\;\;\;\;逆序對就是一對數滿足 i<j&&nums[i]>nu…

三極管三種基本放大電路:共射、共集、共基放大電路

文章目錄一、共集放大電路1.靜態分析2.動態分析二、共基放大電路1.靜態分析2.動態分析總結如何判斷共射、共集、共基放大電路&#xff1f; 電路的輸入回路與輸出回路以發射極為公共端的電路稱為共射放大電路。 電路的輸入回路與輸出回路以集電極為公共端的電路稱為共集放大電路…

Function AI 助力用戶自主開發 MCP 服務,一鍵上云高效部署

作者&#xff1a;靖蘇 在 AI 與云原生協同創新的浪潮下&#xff0c;多模型、多場景智能應用日益普及。開發者面臨的首要挑戰&#xff0c;是如何實現模型之間、服務之間的高效協同&#xff0c;以及如何便捷地將自主研發能力拓展到云端&#xff0c;形成靈活可擴展的智能服務。MC…

c++編譯環境安裝(gcc、cmake)

一、gcc下載 下載地址&#xff1a;https://ftp.gnu.org/gnu/gcc/ 選擇想要下載的版本&#xff0c;然后解壓&#xff0c;查看 contrib/download_prerequisites 中的依賴。 以我下載的 gcc-7.3.0 為例&#xff0c; 二、安裝依賴包 【gmp】 https://ftp.gnu.org/gnu/gmp/ 【is…

基于貝葉斯的營銷組合模型實戰案例(PyMC實踐)

文章出自&#xff1a;基于營銷預算優化的媒體投入分配研究 本篇技術亮點在于結合了廣告飽和度和累積效應&#xff0c;通過數學模型和數值優化方法&#xff0c;精確計算電視與數字媒體的最佳預算分配比例&#xff0c;實現增量銷售最大化。該方法適合有多渠道廣告投放需求、預算…

react_05create-react-app腳手架詳細解析(export)

腳手架是什么&#xff1f; 是一種工具:快速生成項目的工程化結構&#xff0c;讓項目從搭建到開發&#xff0c;到部署&#xff0c;整個流程變得快速和便捷。 安裝過程: 1.安裝node,安裝完成后驗證版本,出現對應版本就表示成功 node --version npm --version2.React腳手架默認是使…

Uncaught TypeError: Illegal invocation

報錯信息Uncaught TypeError: Illegal invocation關鍵代碼$.operate.post(prefix "/edit", { "taskId": taskId, "taskStatus": completed });<input id"taskId" style"display: none;">[[${completeTask.taskId}]]&…

深入解析Go設計模式:責任鏈模式實戰

什么是責任鏈模式? 責任鏈模式(Chain of Responsibility Pattern)是一種行為設計模式,它通過構建處理者鏈來傳遞請求。每個處理者既能自行決定是否處理當前請求,也可將請求轉交給后續處理者。該模式的核心優勢在于解耦請求發送方與處理方,使多個對象都能獲得處理請求的機…

機器視覺系統工業相機的成像原理及如何選型

機器視覺系統是一種模擬人類視覺功能&#xff0c;通過光學裝置和非接觸式傳感器獲取圖像數據&#xff0c;并進行分析和處理&#xff0c;以實現對目標物體的識別、測量、檢測和定位等功能的智能化系統。其目的是讓機器能夠理解和解釋視覺信息&#xff0c;從而做出決策或執行任務…

Java如何快速實現短信登錄?

全文目錄&#xff1a;開篇語前言1. 短信登錄的工作原理2. 短信登錄的優點3. 短信登錄的缺點4. 短信登錄的實現示例&#xff1a;使用 Java 實現短信登錄的流程4.1 發送短信驗證碼&#xff08;偽代碼&#xff09;4.2 使用第三方短信平臺發送短信&#xff08;以阿里云為例&#xf…

HTML已死,HTML萬歲——重新思考DOM的底層設計理念

每周跟蹤AI熱點新聞動向和震撼發展 想要探索生成式人工智能的前沿進展嗎&#xff1f;訂閱我們的簡報&#xff0c;深入解析最新的技術突破、實際應用案例和未來的趨勢。與全球數同行一同&#xff0c;從行業內部的深度分析和實用指南中受益。不要錯過這個機會&#xff0c;成為AI領…

客戶管理系統的詳細項目框架結構

以下是針對客戶管理系統的詳細項目框架結構&#xff0c;整合了核心業務模塊&#xff08;客戶信息、合同管理、售前售后等&#xff09;&#xff0c;并補充了實用擴展模塊&#xff08;如數據統計、標簽管理等&#xff09;&#xff0c;嚴格遵循Django模塊化設計原則&#xff1a; c…

【01】OpenCV C#——C#開發環境OpenCvSharp 環境配置 工程搭建 及代碼測試

文章目錄一、OpenCV 介紹二、OpenCvSharp 介紹三、OpenCvSharp環境搭建3.1 創建新項目3.2 添加 NuGet組件3.3 代碼測試3.4 相較于 C OpenCV不同的之處四、LearnOpenCV有時候&#xff0c;單純c#做前端時會聯合C實現的dll來落地某些功能由于有時候會用C - Opencv實現算法后封裝成…

【解決辦法】報錯Found dtype Long but expected Float

Found dtype Long but expected Float錯誤通常發生在嘗試將一個數據類型為Long的張量傳遞給一個期望數據類型為Float的函數或操作時。在PyTorch中&#xff0c;Long和Float是兩種常見的數據類型&#xff0c;分別對應于64位整數和32位浮點數。某些函數或操作可能只接受特定數據類…

QtC++ 調用 tesseract開源庫 搭配 Opencv 實現文字識別:從tesseract庫基本介紹到實際應用實現

前言 在當今數字化時代&#xff0c;文字識別&#xff08;OCR&#xff09;技術已經滲透到我們生活和工作的方方面面&#xff0c;從掃描文檔的自動排版到車牌識別、票據信息提取等&#xff0c;都離不開 OCR 技術的支持。而在眾多 OCR 實現方案中&#xff0c;QtC 結合 tesseract 和…