Try to stream the file in handler.

This commit is contained in:
Florian THIERRY
2026-07-24 15:38:36 +02:00
parent f8f86eeae1
commit cef96884c6
6 changed files with 35 additions and 17 deletions
+1
View File
@@ -1,3 +1,4 @@
#[derive(Debug)]
pub enum VideoError { pub enum VideoError {
VideoDoesNotExists, VideoDoesNotExists,
FolderDoesNotExists, FolderDoesNotExists,
+2 -1
View File
@@ -2,7 +2,7 @@ extern crate core;
use actix_web::web::ServiceConfig; use actix_web::web::ServiceConfig;
use actix_web::{get, web, HttpResponse, Responder}; use actix_web::{get, web, HttpResponse, Responder};
use crate::video::handler::{download_file, get_root_folder_content}; use crate::video::handler::{download_file, get_root_folder_content, get_video_stream};
pub mod user; pub mod user;
pub mod video; pub mod video;
@@ -14,6 +14,7 @@ async fn health() -> impl Responder {
pub fn init(cfg: &mut ServiceConfig) { pub fn init(cfg: &mut ServiceConfig) {
cfg.service(download_file) cfg.service(download_file)
.service(get_video_stream)
.service(get_root_folder_content); .service(get_root_folder_content);
cfg.default_service(web::to(not_found)); cfg.default_service(web::to(not_found));
} }
+18 -10
View File
@@ -28,22 +28,30 @@ pub async fn get_video_stream(
) -> impl Responder { ) -> impl Responder {
let video_result = video_service.get_video_stream(video_id.into_inner()); let video_result = video_service.get_video_stream(video_id.into_inner());
match video_result { match video_result {
Ok(video) => { Ok(video) => {
if let Some(tokioVideoFile) = video.file.as_any().downcast_ref::<TokioVideoFile>() { if let Some(tokio_video_file) = video.file.as_any().downcast_ref::<TokioVideoFile>() {
HttpResponse::Ok() // Clone the file to extend its lifetime into the streaming closure
.content_type("application/octet-stream") let cloned_file = tokio_video_file.file.try_clone();
.streaming(async_stream::stream! {
let mut stream = FramedRead::new(tokioVideoFile.file, BytesCodec::new()); match cloned_file {
while let Some(chunk) = stream.next().await { Ok(file) => HttpResponse::Ok()
yield chunk.map(|bytes| bytes.freeze()); .content_type("application/octet-stream")
} .streaming(async_stream::stream! {
}) let mut stream = FramedRead::new(file, BytesCodec::new());
while let Some(chunk) = stream.next().await {
yield chunk.map(|bytes| bytes.freeze());
}
}),
Err(_) => HttpResponse::InternalServerError().body("Failed to clone video file")
}
} else { } else {
HttpResponse::InternalServerError().body("Video file format is not handled.") HttpResponse::InternalServerError().body("Video file format is not handled.")
} }
} }
Err(error) => HttpResponse::InternalServerError().body("C P T") Err(error) => HttpResponse::InternalServerError().body(format!("{:?}", error))
} }
} }
+1 -1
View File
@@ -123,7 +123,7 @@ impl VideoPort for LibraryItemAdapter {
id: video.id, id: video.id,
name: video.name, name: video.name,
format: AVI, format: AVI,
file: Box::new(TokioVideoFile::new(&video_file_path).map_err(|_| VideoError::FileReadingTechnicalIssue)?) file: Box::new(TokioVideoFile::new(&video_file_path))
}; };
Ok(result) Ok(result)
+5 -5
View File
@@ -1,6 +1,6 @@
use std::any::Any; use std::any::Any;
use std::fs::File;
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
use tokio::fs::File;
use uuid::Uuid; use uuid::Uuid;
use lumiere_domain::video::model::library_item::{LibraryItem, LibraryItemType, LibraryRawItem}; use lumiere_domain::video::model::library_item::{LibraryItem, LibraryItemType, LibraryRawItem};
use lumiere_domain::video::model::video::VideoFile; use lumiere_domain::video::model::video::VideoFile;
@@ -10,10 +10,10 @@ pub struct TokioVideoFile {
} }
impl TokioVideoFile { impl TokioVideoFile {
pub fn new(file_path: &String) -> Result<Self, Error> { pub fn new(file_path: &String) -> Self {
let rt = tokio::runtime::Runtime::new()?; Self {
let file = rt.block_on(async { File::open(file_path).await })?; file: File::open(file_path).expect(format!("Failed to open file {file_path}").as_str())
Ok(Self { file }) }
} }
} }
+8
View File
@@ -9,4 +9,12 @@
</div> </div>
} }
</div> </div>
<div>
@if (firstVideo(); as firstVideo) {
<video controls>
<source src="/api/videos/{{firstVideo.id}}/stream" type="video/mp4">
Your browser does not support the html video player.
</video>
}
</div>
<router-outlet /> <router-outlet />