惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
腾讯CDC
阮一峰的网络日志
阮一峰的网络日志
博客园_首页
Last Week in AI
Last Week in AI
月光博客
月光博客
D
DataBreaches.Net
WordPress大学
WordPress大学
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 叶小钗
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
U
Unit 42
Recent Announcements
Recent Announcements
宝玉的分享
宝玉的分享
MyScale Blog
MyScale Blog
C
Check Point Blog
F
Fortinet All Blogs
B
Blog
小众软件
小众软件
Vercel News
Vercel News
罗磊的独立博客
有赞技术团队
有赞技术团队

Yesterday17's Blog

2026 新年解密红包 / Melody Flag | Yesterday17's Blog 谈谈 Iori 的设计思路(一):从 Nico Timeshift 说起 | Yesterday17's Blog Iori Minyami 0.1.0 发布 | Yesterday17's Blog 2025 新年解密红包 / Melody Flag | Yesterday17's Blog 使用 Cloudflare Warp 解决罗森票务的海外登录问题 | Yesterday17's Blog How To Blog 04: The Astro v5 Era | Yesterday17's Blog 谈谈 tokio::select! 的公平性 | Yesterday17's Blog Learning Pingora 05 - Connect with TLS | Yesterday17's Blog Leaving Bytedance | Yesterday17's Blog 大橋彩香 AsiaTour「Reflection」上海公演 个人向记录 & Repo | Yesterday17's Blog Recoving from burnout - What happened? | Yesterday17 Yubikey 重建手册 | Yesterday17's Blog How To Blog 03: Heimus | Yesterday17's Blog 🪧 Blog Migration Accouncement | Yesterday17's Blog Learn Your IDE - VSCode 是如何仅重启插件的? | Yesterday17's Blog How To Blog 02: Astro❤️Password | Yesterday17's Blog How To Blog 01: Why, How, and the Future | Yesterday17's Blog Learning Pingora 04 - Establish L4 Connection | Yesterday17's Blog Learning Pingora 03 - Upstreams and Peers | Yesterday17's Blog Learning Pingora 02 - A Simple HTTP Server | Yesterday17's Blog Learning Pingora 01 - Getting Started | Yesterday17's Blog 2024 新年解密红包 / Melody Flag | Yesterday17's Blog 向新的一年飞驰——记录 2023 | Yesterday17's Blog 「サクラノ刻」对话选摘(2) | Yesterday17's Blog PGP Key Revocation 注销声明 | Yesterday17's Blog 「サクラノ刻」对话选摘(1) | Yesterday17's Blog 2023 新年解密红包 / Melody Flag | Yesterday17's Blog 『蒼の彼方のフォーリズム』通关感想 | Yesterday17's Blog 单显卡直通教程 | Yesterday17's Blog 对博客与笔记的思考 | Yesterday17's Blog
谈谈 Iori 的设计思路(二):如何实现一个 Showroom 录制工...
Yesterday17 · 2025-05-11 · via Yesterday17's Blog

大家好,好久不见,我是某昨。

iori 的一个重要设计目标是能够提供简单易用的下载核心,方便各种下载器遍地开花。在 iori 诞生后,出现了 iori-minyamishiori 两个下载工具。这两个工具都是相对通用的下载器,可以满足绝大多数的 HLS 下载需求。但当我们需要进行一些定制时,则需要回到 iori 这个下载核心本身。

由于相对完善的设计,因此直接使用 iori 进行定制下载也非常简单。这里我们就用 Showroom 下载为例,看看这样的一个定制需求要如何实现吧(

ToC

  • 描述一下
  • 分析一下
  • 实现一下

描述一下

首先来描述一下需求。

我们需要的是一个简单的 Showroom 下载工具。它的功能是监听复数个 Showroom 房间的开播情况,并且一旦房间开始直播,就将直播内容进行一个录制。
由于服务器的磁盘空间有限,而带宽和流量相对较足,因此我们希望可以将下载得到的 ts 块直接上传到指定的 S3(Cloudflare R2) 桶中。
Showroom 的录制中可能会出现各种奇怪的错误,具体可以参考院士的《Showroom直播系统录制器的坑与解决方案》。

分析一下

iori 将下载的过程抽象为了 SourceCacheMerger 三部分,Downloader 分别调用这三个部分进行下载。

source 层我们可以直接复用 hls::CommonM3u8LiveSource。虽然一些细节上可能会有些问题,但大部分场景还是可以满足的。
由于我们是希望将 ts 块直接上传到 S3,因此我们可以直接将 S3 作为这个下载器的 Cacheiori 最近引入了 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 则是分别定义了上面提到的 SourceCacheMerger;最后 L34-L38 构造 Downloader 下载就可以啦(

完整的代码可以参考: https://github.com/Yesterday17/iori/blob/master/bin/srr/src/main.rs