大家好,好久不见,我是某昨。
iori
的一个重要设计目标是能够提供简单易用的下载核心,方便各种下载器遍地开花。在 iori
诞生后,出现了 iori-minyami
和 shiori
两个下载工具。这两个工具都是相对通用的下载器,可以满足绝大多数的 HLS
下载需求。但当我们需要进行一些定制时,则需要回到 iori
这个下载核心本身。
由于相对完善的设计,因此直接使用 iori
进行定制下载也非常简单。这里我们就用 Showroom
下载为例,看看这样的一个定制需求要如何实现吧(
ToC
描述一下
首先来描述一下需求。
我们需要的是一个简单的 Showroom 下载工具。它的功能是监听复数个 Showroom 房间的开播情况,并且一旦房间开始直播,就将直播内容进行一个录制。
由于服务器的磁盘空间有限,而带宽和流量相对较足,因此我们希望可以将下载得到的 ts
块直接上传到指定的 S3
(Cloudflare R2
) 桶中。
Showroom 的录制中可能会出现各种奇怪的错误,具体可以参考院士的《Showroom直播系统录制器的坑与解决方案》。
分析一下
iori
将下载的过程抽象为了 Source
、Cache
和 Merger
三部分,Downloader
分别调用这三个部分进行下载。
source
层我们可以直接复用 hls::CommonM3u8LiveSource
。虽然一些细节上可能会有些问题,但大部分场景还是可以满足的。
由于我们是希望将 ts
块直接上传到 S3
,因此我们可以直接将 S3
作为这个下载器的 Cache
。iori
最近引入了 opendal
的支持,因此可以非常轻松地将下载得到的块保存到 S3
中。
而最后的 Merger
,由于我们只需要 ts
分块,因此 merger
对这个场景其实是不需要的。
实现一下
至此,我们已经理清了这个下载器需要的所有能力究竟要调用哪部分模块,接下来就是积木时间——
async fn record_room( client: ShowRoomClient, room_slug: &str, room_id: u64, operator: Operator,) -> anyhow::Result<()> { log::debug!("Attempt to record room {room_slug}, id = {room_id}");
let room_info = client.room_profile(room_id).await?; if !room_info.is_live() { log::debug!("Room {room_slug} is not live, skipping..."); return Ok(()); }
let stream = client.live_streaming_url(room_id).await?; let Some(stream) = stream.best(false) else { log::debug!("Room {room_slug} is not live, skipping..."); return Ok(()); };
let live_id = room_info.live_id; let live_started_at = chrono::DateTime::from_timestamp(room_info.current_live_started_at, 0) .unwrap() .with_timezone(&chrono_tz::Asia::Tokyo) .to_rfc3339(); let prefix = format!("{room_slug}/{live_id}_{live_started_at}");
let client = HttpClient::default(); let source = CommonM3u8LiveSource::new(client, stream.url.clone(), None, None); let cache = IoriCache::opendal(operator.clone(), prefix, false); let merger = IoriMerger::skip();
log::info!("Start recording room {room_slug}, id = {room_id}, live_id = {live_id}"); ParallelDownloaderBuilder::new() .cache(cache) .merger(merger) .download(source) .await?;
Ok(())}
简单解释一下。L9-L19
都是在获取 Showroom 的 m3u8 地址;L21-L26
定义了 ts 块在 S3 中的前缀;L28-L31 则是分别定义了上面提到的 Source
、Cache
和 Merger
;最后 L34-L38
构造 Downloader
下载就可以啦(
完整的代码可以参考: https://github.com/Yesterday17/iori/blob/master/bin/srr/src/main.rs