feat: add 'Range' header support
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Should allow for seeking.
This commit is contained in:
parent
79e2f730cc
commit
9d5373797c
4 changed files with 281 additions and 242 deletions
436
Cargo.lock
generated
436
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -23,7 +23,7 @@ quick-xml = { version = "0.31", features = ["serialize"] }
|
|||
serde = { workspace = true }
|
||||
serde_json = "1.0"
|
||||
time = { workspace = true, features = ["local-offset"] }
|
||||
tokio = { version = "1.34", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { version = "0.3", features = [
|
||||
"env-filter",
|
||||
|
|
@ -37,11 +37,12 @@ url-escape = "0.1"
|
|||
sea-orm = { workspace = true }
|
||||
entities = { workspace = true }
|
||||
migration = { workspace = true }
|
||||
once_cell = { version = "1.18", features = ["parking_lot"] }
|
||||
once_cell = { version = "1", features = ["parking_lot"] }
|
||||
futures = "0.3"
|
||||
audiotags = "0.4"
|
||||
tracing-appender = "0.2"
|
||||
blake3 = "1.5"
|
||||
image = "0.24"
|
||||
nate = "0.4"
|
||||
rand = "0.8.5"
|
||||
rand = "0.8"
|
||||
http-range = "0.1"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use poem::{http::StatusCode, Endpoint, EndpointExt, Response, Route};
|
||||
use poem::{
|
||||
http::{header, StatusCode},
|
||||
Endpoint, EndpointExt, Response, Route,
|
||||
};
|
||||
|
||||
// rest/getLicense
|
||||
mod get_license;
|
||||
|
|
@ -43,8 +46,16 @@ pub fn build() -> Box<dyn Endpoint<Output = poem::Response>> {
|
|||
.at("/getAlbumList2.view", get_album_list::get_album_list)
|
||||
.at("/getAlbum", get_album::get_album)
|
||||
.at("/getAlbum.view", get_album::get_album)
|
||||
.at("/stream", stream::stream)
|
||||
.at("/stream.view", stream::stream)
|
||||
.at(
|
||||
"/stream",
|
||||
stream::stream
|
||||
.with(poem::middleware::SetHeader::new().appending(header::ACCEPT_RANGES, "bytes")),
|
||||
)
|
||||
.at(
|
||||
"/stream.view",
|
||||
stream::stream
|
||||
.with(poem::middleware::SetHeader::new().appending(header::ACCEPT_RANGES, "bytes")),
|
||||
)
|
||||
.at("/startScan", start_scan::start_scan)
|
||||
.at("/startScan.view", start_scan::start_scan)
|
||||
.at("/getScanStatus", get_scan_status::get_scan_status)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use crate::{json_or_xml, utils::db::DbTxn};
|
||||
use entities::prelude::Track;
|
||||
use http_range::HttpRange;
|
||||
use poem::{
|
||||
http::StatusCode,
|
||||
http::{header, HeaderMap, StatusCode},
|
||||
web::{Data, Query},
|
||||
Response,
|
||||
};
|
||||
|
|
@ -20,6 +21,7 @@ use crate::{
|
|||
pub async fn stream(
|
||||
Data(txn): Data<&DbTxn>,
|
||||
auth: Authentication,
|
||||
headers: &HeaderMap,
|
||||
Query(params): Query<StreamParams>,
|
||||
) -> Response {
|
||||
let u = utils::verify_user(txn.clone(), &auth).await;
|
||||
|
|
@ -81,11 +83,62 @@ pub async fn stream(
|
|||
}
|
||||
};
|
||||
|
||||
if let Some(range) = headers.get(header::RANGE) {
|
||||
let range = match range.to_str() {
|
||||
Ok(range) => range,
|
||||
Err(e) => {
|
||||
error!(
|
||||
error = &e as &dyn std::error::Error,
|
||||
"Error parsing range header: {e}"
|
||||
);
|
||||
return json_or_xml!(auth, SubsonicResponse::new_error(Error::Generic(None)));
|
||||
}
|
||||
};
|
||||
|
||||
let range = match HttpRange::parse(range, song.len() as u64) {
|
||||
Ok(range) => range,
|
||||
Err(e) => {
|
||||
error!("Error parsing range header: {e:?}");
|
||||
return json_or_xml!(auth, SubsonicResponse::new_error(Error::Generic(None)));
|
||||
}
|
||||
};
|
||||
|
||||
if range.len() > 1 {
|
||||
return json_or_xml!(
|
||||
auth,
|
||||
SubsonicResponse::new_error(Error::Generic(Some(
|
||||
"Multiple ranges are not supported".to_string()
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(range) = range.first() {
|
||||
let body = song[range.start as usize..(range.start + range.length) as usize].to_vec();
|
||||
|
||||
poem::Response::builder()
|
||||
.status(StatusCode::PARTIAL_CONTENT)
|
||||
.header(header::CONTENT_TYPE, track.content_type)
|
||||
.header(
|
||||
header::CONTENT_RANGE,
|
||||
format!("bytes {}-{}/{}", range.start, range.length, song.len()),
|
||||
)
|
||||
.header(header::CONTENT_LENGTH, body.len())
|
||||
.body(body)
|
||||
} else {
|
||||
poem::Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/mpeg")
|
||||
.header(header::CONTENT_TYPE, track.content_type)
|
||||
.header(header::CONTENT_LENGTH, song.len())
|
||||
.body(song)
|
||||
}
|
||||
} else {
|
||||
poem::Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, track.content_type)
|
||||
.header(header::CONTENT_LENGTH, song.len())
|
||||
.body(song)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
pub struct StreamParams {
|
||||
|
|
|
|||
Loading…
Reference in a new issue