mirror of
https://github.com/imputnet/cobalt.git
synced 2026-10-24 02:52:18 +00:00
api & packages: rewrite hls parser with native rust module
This commit is contained in:
parent
a636575b09
commit
a408f69a38
12 changed files with 1910 additions and 58 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -20,3 +20,7 @@ docker-compose.yml
|
|||
|
||||
# ide
|
||||
.vscode
|
||||
|
||||
# rust
|
||||
target/
|
||||
*.node
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"@datastructures-js/priority-queue": "^6.3.5",
|
||||
"@imput/psl": "^2.0.4",
|
||||
"@imput/version-info": "workspace:^",
|
||||
"@imput/cobalt-rs-hls": "workspace:*",
|
||||
"content-disposition-header": "0.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import HLS from "hls-parser";
|
||||
import { createInternalStream } from "./manage.js";
|
||||
import { request } from "undici";
|
||||
import { HlsParser } from "@imput/cobalt-rs-hls";
|
||||
|
||||
function getURL(url) {
|
||||
try {
|
||||
|
|
@ -10,50 +10,6 @@ function getURL(url) {
|
|||
}
|
||||
}
|
||||
|
||||
function transformObject(streamInfo, hlsObject) {
|
||||
if (hlsObject === undefined) {
|
||||
return (object) => transformObject(streamInfo, object);
|
||||
}
|
||||
|
||||
let fullUrl;
|
||||
if (getURL(hlsObject.uri)) {
|
||||
fullUrl = new URL(hlsObject.uri);
|
||||
} else {
|
||||
fullUrl = new URL(hlsObject.uri, streamInfo.url);
|
||||
}
|
||||
|
||||
if (fullUrl.hostname !== '127.0.0.1') {
|
||||
hlsObject.uri = createInternalStream(fullUrl.toString(), streamInfo);
|
||||
|
||||
if (hlsObject.map) {
|
||||
hlsObject.map = transformObject(streamInfo, hlsObject.map);
|
||||
}
|
||||
}
|
||||
|
||||
return hlsObject;
|
||||
}
|
||||
|
||||
function transformMasterPlaylist(streamInfo, hlsPlaylist) {
|
||||
const makeInternalStream = transformObject(streamInfo);
|
||||
|
||||
const makeInternalVariants = (variant) => {
|
||||
variant = transformObject(streamInfo, variant);
|
||||
variant.video = variant.video.map(makeInternalStream);
|
||||
variant.audio = variant.audio.map(makeInternalStream);
|
||||
return variant;
|
||||
};
|
||||
hlsPlaylist.variants = hlsPlaylist.variants.map(makeInternalVariants);
|
||||
|
||||
return hlsPlaylist;
|
||||
}
|
||||
|
||||
function transformMediaPlaylist(streamInfo, hlsPlaylist) {
|
||||
const makeInternalSegments = transformObject(streamInfo);
|
||||
hlsPlaylist.segments = hlsPlaylist.segments.map(makeInternalSegments);
|
||||
hlsPlaylist.prefetchSegments = hlsPlaylist.prefetchSegments.map(makeInternalSegments);
|
||||
return hlsPlaylist;
|
||||
}
|
||||
|
||||
const HLS_MIME_TYPES = ["application/vnd.apple.mpegurl", "audio/mpegurl", "application/x-mpegURL"];
|
||||
|
||||
export function isHlsResponse(req, streamInfo) {
|
||||
|
|
@ -64,16 +20,14 @@ export function isHlsResponse(req, streamInfo) {
|
|||
}
|
||||
|
||||
export async function handleHlsPlaylist(streamInfo, req, res) {
|
||||
let hlsPlaylist = await req.body.text();
|
||||
hlsPlaylist = HLS.parse(hlsPlaylist);
|
||||
const playlistContent = await req.body.text();
|
||||
|
||||
hlsPlaylist = hlsPlaylist.isMasterPlaylist
|
||||
? transformMasterPlaylist(streamInfo, hlsPlaylist)
|
||||
: transformMediaPlaylist(streamInfo, hlsPlaylist);
|
||||
const parser = new HlsParser(streamInfo.url);
|
||||
const result = parser.handleHlsPlaylist(playlistContent, (uri) => {
|
||||
return createInternalStream(uri, streamInfo);
|
||||
});
|
||||
|
||||
hlsPlaylist = HLS.stringify(hlsPlaylist);
|
||||
|
||||
res.send(hlsPlaylist);
|
||||
res.send(result);
|
||||
}
|
||||
|
||||
async function getSegmentSize(url, config) {
|
||||
|
|
@ -109,15 +63,23 @@ export async function probeInternalHLSTunnel(streamInfo) {
|
|||
const config = { headers, dispatcher, signal, maxRedirections: 16 };
|
||||
|
||||
const manifestResponse = await fetch(url, config);
|
||||
const manifestText = await manifestResponse.text();
|
||||
|
||||
const manifest = HLS.parse(await manifestResponse.text());
|
||||
if (manifest.segments.length === 0)
|
||||
const parser = new HlsParser(streamInfo.url);
|
||||
let segments;
|
||||
try {
|
||||
segments = parser.getMediaSegments(manifestText);
|
||||
} catch (e) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!segments || segments.length === 0)
|
||||
return -1;
|
||||
|
||||
const segmentSamples = await Promise.all(
|
||||
Array(5).fill().map(async () => {
|
||||
const manifestIdx = Math.floor(Math.random() * manifest.segments.length);
|
||||
const randomSegment = manifest.segments[manifestIdx];
|
||||
const manifestIdx = Math.floor(Math.random() * segments.length);
|
||||
const randomSegment = segments[manifestIdx];
|
||||
if (!randomSegment.uri)
|
||||
throw "segment is missing URI";
|
||||
|
||||
|
|
@ -135,7 +97,7 @@ export async function probeInternalHLSTunnel(streamInfo) {
|
|||
);
|
||||
|
||||
const averageBitrate = segmentSamples.reduce((a, b) => a + b) / segmentSamples.length;
|
||||
const totalDuration = manifest.segments.reduce((acc, segment) => acc + segment.duration, 0);
|
||||
const totalDuration = segments.reduce((acc, segment) => acc + segment.duration, 0);
|
||||
|
||||
return averageBitrate * totalDuration;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ requirements:
|
|||
- node.js >= 18
|
||||
- git
|
||||
- pnpm
|
||||
- rust (cargo) for the high-performance HLS parsing module
|
||||
|
||||
1. clone the repo: `git clone https://github.com/imputnet/cobalt`.
|
||||
2. go to api directory: `cd cobalt/api`.
|
||||
|
|
|
|||
572
packages/cobalt-rs-hls/Cargo.lock
generated
Normal file
572
packages/cobalt-rs-hls/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,572 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cobalt-rs-hls"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"m3u8-rs",
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"utf8_iter",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||
|
||||
[[package]]
|
||||
name = "m3u8-rs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f03cd3335fb5f2447755d45cda9c70f76013626a9db44374973791b0926a86c3"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "2.16.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ctor",
|
||||
"napi-derive",
|
||||
"napi-sys",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-build"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1"
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive"
|
||||
version = "2.16.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"convert_case",
|
||||
"napi-derive-backend",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-backend"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"semver",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-sys"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
16
packages/cobalt-rs-hls/Cargo.toml
Normal file
16
packages/cobalt-rs-hls/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
name = "cobalt-rs-hls"
|
||||
version = "0.0.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
napi = { version = "2.16.2", default-features = false, features = ["napi4"] }
|
||||
napi-derive = "2.16.2"
|
||||
m3u8-rs = "6.0.0"
|
||||
url = "2.5.0"
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.1.3"
|
||||
5
packages/cobalt-rs-hls/build.rs
Normal file
5
packages/cobalt-rs-hls/build.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
extern crate napi_build;
|
||||
|
||||
fn main() {
|
||||
napi_build::setup();
|
||||
}
|
||||
0
packages/cobalt-rs-hls/index.d.ts
vendored
Normal file
0
packages/cobalt-rs-hls/index.d.ts
vendored
Normal file
7
packages/cobalt-rs-hls/index.js
Normal file
7
packages/cobalt-rs-hls/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
// we load the compiled native binary directly for now
|
||||
const { HlsParser } = require('./cobalt-rs-hls.darwin-arm64.node');
|
||||
|
||||
export { HlsParser };
|
||||
21
packages/cobalt-rs-hls/package.json
Normal file
21
packages/cobalt-rs-hls/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "@imput/cobalt-rs-hls",
|
||||
"version": "11.7.0",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
"name": "cobalt-rs-hls",
|
||||
"triples": {
|
||||
"defaults": true,
|
||||
"additional": []
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "napi build --platform --release",
|
||||
"build:debug": "napi build --platform"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^3.0.0"
|
||||
}
|
||||
}
|
||||
125
packages/cobalt-rs-hls/src/lib.rs
Normal file
125
packages/cobalt-rs-hls/src/lib.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#![deny(clippy::all)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
||||
use m3u8_rs::{MasterPlaylist, MediaPlaylist, Playlist};
|
||||
use napi::bindgen_prelude::*;
|
||||
use napi::JsString;
|
||||
use url::Url;
|
||||
|
||||
#[napi(object)]
|
||||
pub struct MediaSegment {
|
||||
pub uri: String,
|
||||
pub duration: f64,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub struct HlsParser {
|
||||
stream_info_url: String,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl HlsParser {
|
||||
#[napi(constructor)]
|
||||
pub fn new(stream_info_url: String) -> Self {
|
||||
Self { stream_info_url }
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn handle_hls_playlist(
|
||||
&self,
|
||||
env: Env,
|
||||
playlist_content: String,
|
||||
create_internal_stream: JsFunction,
|
||||
) -> Result<String> {
|
||||
// parse the raw hls text
|
||||
match m3u8_rs::parse_playlist_res(playlist_content.as_bytes()) {
|
||||
Ok(Playlist::MasterPlaylist(mut master)) => {
|
||||
self.transform_master_playlist(&env, &mut master, &create_internal_stream)?;
|
||||
let mut buf = Vec::new();
|
||||
master.write_to(&mut buf).map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;
|
||||
Ok(String::from_utf8(buf).unwrap())
|
||||
}
|
||||
Ok(Playlist::MediaPlaylist(mut media)) => {
|
||||
self.transform_media_playlist(&env, &mut media, &create_internal_stream)?;
|
||||
let mut buf = Vec::new();
|
||||
media.write_to(&mut buf).map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;
|
||||
Ok(String::from_utf8(buf).unwrap())
|
||||
}
|
||||
Err(e) => {
|
||||
Err(Error::new(Status::InvalidArg, format!("failed to parse hls: {:?}", e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_media_segments(&self, playlist_content: String) -> Result<Vec<MediaSegment>> {
|
||||
match m3u8_rs::parse_playlist_res(playlist_content.as_bytes()) {
|
||||
Ok(Playlist::MediaPlaylist(media)) => {
|
||||
let segments = media.segments.into_iter().map(|s| MediaSegment {
|
||||
uri: s.uri,
|
||||
duration: s.duration as f64,
|
||||
}).collect();
|
||||
Ok(segments)
|
||||
}
|
||||
Ok(Playlist::MasterPlaylist(_)) => {
|
||||
Err(Error::new(Status::InvalidArg, "expected media playlist, got master".to_string()))
|
||||
}
|
||||
Err(e) => {
|
||||
Err(Error::new(Status::InvalidArg, format!("failed to parse hls: {:?}", e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_url(&self, uri: &str) -> Option<Url> {
|
||||
if let Ok(full_url) = Url::parse(uri) {
|
||||
Some(full_url)
|
||||
} else if let Ok(base_url) = Url::parse(&self.stream_info_url) {
|
||||
base_url.join(uri).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_uri(
|
||||
&self,
|
||||
env: &Env,
|
||||
uri: &mut String,
|
||||
create_internal_stream: &JsFunction,
|
||||
) -> Result<()> {
|
||||
if let Some(full_url) = self.resolve_url(uri) {
|
||||
if full_url.host_str() != Some("127.0.0.1") {
|
||||
// call back into node to create the internal stream
|
||||
let js_string = env.create_string(&full_url.to_string())?;
|
||||
let result: JsString = create_internal_stream.call(None, &[js_string])?.try_into()?;
|
||||
*uri = result.into_utf8()?.into_owned()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_master_playlist(
|
||||
&self,
|
||||
env: &Env,
|
||||
master: &mut MasterPlaylist,
|
||||
create_internal_stream: &JsFunction,
|
||||
) -> Result<()> {
|
||||
for variant in &mut master.variants {
|
||||
self.transform_uri(env, &mut variant.uri, create_internal_stream)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_media_playlist(
|
||||
&self,
|
||||
env: &Env,
|
||||
media: &mut MediaPlaylist,
|
||||
create_internal_stream: &JsFunction,
|
||||
) -> Result<()> {
|
||||
for segment in &mut media.segments {
|
||||
self.transform_uri(env, &mut segment.uri, create_internal_stream)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
1138
pnpm-lock.yaml
generated
1138
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue