feat(backend): serve video with HTTP range requests
This commit is contained in:
@@ -2,6 +2,7 @@ use axum::{
|
||||
Json, Router,
|
||||
body::Body,
|
||||
extract::{Multipart, Path, State},
|
||||
http::{HeaderMap, Response, StatusCode, header},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
};
|
||||
@@ -19,6 +20,7 @@ pub fn routes_file(file_repository: FileRepository) -> Router {
|
||||
.route("/files", get(list_files).post(upload_file))
|
||||
.route("/files/{id}", get(get_file).delete(delete_file))
|
||||
.route("/files/{id}/download", get(download_file))
|
||||
.route("/files/{id}/stream_part", get(stream_part))
|
||||
.with_state(file_repository)
|
||||
}
|
||||
|
||||
@@ -54,7 +56,6 @@ async fn upload_file(
|
||||
Err(LoftError::UndefinedErrorType)
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
async fn get_file(
|
||||
State(file_repository): State<FileRepository>,
|
||||
ctx: Ctx,
|
||||
@@ -66,7 +67,6 @@ async fn get_file(
|
||||
Ok(Json(record))
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
async fn download_file(
|
||||
State(file_repository): State<FileRepository>,
|
||||
ctx: Ctx,
|
||||
@@ -78,6 +78,49 @@ async fn download_file(
|
||||
Ok(Body::from_stream(stream))
|
||||
}
|
||||
|
||||
async fn stream_part(
|
||||
State(file_repository): State<FileRepository>,
|
||||
ctx: Ctx,
|
||||
headers: HeaderMap,
|
||||
Path(file_id): Path<u64>,
|
||||
) -> Result<impl IntoResponse, LoftError> {
|
||||
info!("stream_part");
|
||||
let file_record = file_repository
|
||||
.get_file(file_id as i64, ctx.user_id())
|
||||
.await?;
|
||||
let file_size = file_record.size as u64;
|
||||
let (start, end): (u64, u64) = parse_range(&headers, file_size);
|
||||
|
||||
let stream = file_repository
|
||||
.stream_part(file_id as i64, ctx.user_id(), start, end + 1)
|
||||
.await?;
|
||||
|
||||
let respones = Response::builder()
|
||||
.status(StatusCode::PARTIAL_CONTENT)
|
||||
.header(header::CONTENT_TYPE, file_record.file_type)
|
||||
.header(header::CONTENT_LENGTH, end - start + 1)
|
||||
.header(
|
||||
header::CONTENT_RANGE,
|
||||
format!("bytes {start}-{end}/{file_size}"),
|
||||
)
|
||||
.header(header::ACCEPT_RANGES, "bytes")
|
||||
.body(Body::from_stream(stream))
|
||||
.unwrap();
|
||||
|
||||
Ok(respones)
|
||||
}
|
||||
|
||||
fn parse_range(headers: &HeaderMap, file_size: u64) -> (u64, u64) {
|
||||
let range = headers.get(header::RANGE);
|
||||
let str = range.unwrap().to_str().unwrap();
|
||||
let strip = str.strip_prefix("bytes=").unwrap();
|
||||
let tuple = strip.split_once("-").unwrap();
|
||||
let start = tuple.0.parse::<u64>().unwrap();
|
||||
let end = tuple.1.parse::<u64>().unwrap_or(file_size - 1);
|
||||
|
||||
(start, end)
|
||||
}
|
||||
|
||||
async fn delete_file(
|
||||
State(file_repository): State<FileRepository>,
|
||||
ctx: Ctx,
|
||||
|
||||
Reference in New Issue
Block a user