Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

586 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SJMediaCacheServer

基于本地 HTTP 代理的「边播边缓存」框架:在播放的同时把媒体数据写入本地缓存,优先用缓存数据响应播放器请求,从而减少网络流量、提升起播与播放流畅度。支持 HLS(HTTP Live Streaming)基于文件的媒体(MP3 / AAC / WAV / FLAC / OGG / MP4 / MOV 等),现已完全使用 Swift 6 实现。

Swift 6 SwiftPM Platform License

简介

SJMediaCacheServer 会在本地启动一个 HTTP 代理服务器,并把原始媒体 URL 转换成指向本地代理的「播放地址」。播放器从本地代理读取数据:命中缓存时直接返回缓存内容,未命中时才向远端发起请求,同时把下载到的数据落盘缓存。

它会自动解析 HLS 播放列表(m3u8),对其中的播放列表、AES 加密 Key 以及 ts 分片分别进行代理与缓存;对基于文件的媒体则支持按 Range 分片下载与缓存。整套机制对播放器透明——你只需把播放地址换成代理地址即可。

功能特性

  • 代理播放请求:将原始 URL 转换为本地代理 URL,播放器从本地代理取数据,命中缓存直接返回,未命中再回源,减少网络请求、提升播放速度与可靠性。
  • HLS 支持:自动解析 m3u8 播放列表,分别代理与缓存「播放列表 / AES Key / ts 分片」,支持边播边缓存。
  • 基于文件的媒体支持:支持 MP3、AAC、WAV、FLAC、OGG、MP4、MOV 等常见格式,按 Range 分片缓存。
  • 缓存管理:可配置缓存资源个数上限、磁盘最长保留时长、磁盘最大占用大小,以及设备预留的最小可用磁盘空间。
  • 预加载(Prefetch):支持「整资源预加载」「按字节大小预加载」「按文件个数预加载(适合 HLS 提前缓存若干 ts 分片)」,并可控制最大并发预加载数。
  • 资源导出(Export):将资源导出为离线可用的独立缓存,可查询状态/进度、注册观察者、统计占用大小并单独删除。导出资源与播放/预加载缓存分开管理。
  • 自定义请求:可统一为下载请求追加请求头、为指定资源 URL 按数据类型设置请求头、自定义 URLSessionConfiguration,以及采集网络指标。
  • 数据编解码:写入缓存前可对数据进行编码(加密),读取时再解码,便于实现本地缓存加密。
  • 资源标识解析:当不同 URL 指向同一资源时,可自定义 resolveAssetIdentifier 让它们共用同一份缓存(默认实现会去掉 query 部分)。
  • 失败通知与日志:播放请求失败时通过通知派发;DEBUG 下可开启控制台日志并配置日志级别与选项。

环境要求

  • iOS 15.0+
  • Swift 6(Swift 6 语言模式)
  • 较新版本的 Xcode(建议使用支持 Swift 6 工具链的 Xcode)

安装

仅支持 Swift Package Manager

方式一:Xcode 添加包

  1. 在 Xcode 中选择 File → Add Package Dependencies…

  2. 输入仓库地址:

    https://github.com/moxcomic/SJMediaCacheServer.git
    
  3. 依赖规则选择 Branch → main(或后续发布的 tag),将 SJMediaCacheServer 库添加到你的 target。

方式二:Package.swift

dependencies: [
    .package(url: "https://github.com/moxcomic/SJMediaCacheServer.git", branch: "main")
]

并在对应 target 中声明依赖:

.target(
    name: "YourApp",
    dependencies: [
        .product(name: "SJMediaCacheServer", package: "SJMediaCacheServer")
    ]
)

依赖说明

SJMediaCacheServer 依赖以下两个 Swift Package,SPM 会自动解析,无需手动安装:

快速开始

把原始 URL 通过 playbackURL(with:) 转换为播放地址,再交给播放器;在播放、seek 等场景下调用 setActive(true) 确保代理服务处于激活状态。

import AVFoundation
import SJMediaCacheServer

final class PlayerController {
    private let player: AVPlayer

    init(url: URL) {
        // 将原始 URL 转换为本地代理播放地址
        let playbackURL = SJMediaCacheServer.shared.playbackURL(with: url) ?? url
        player = AVPlayer(url: playbackURL)
    }

    func play() {
        // 进入后台后, 所有连接关闭时代理服务会停止; 播放/seek 前请激活
        SJMediaCacheServer.shared.setActive(true)
        player.play()
    }

    func seek(to time: CMTime) {
        SJMediaCacheServer.shared.setActive(true)
        player.seek(to: time)
    }
}

说明:setActive(true) 启动代理服务,setActive(false) 停止;可通过 SJMediaCacheServer.shared.isActive 查询当前是否运行。当传入的是本地文件 URL 时,playbackURL(with:) 会原样返回。

进阶用法

缓存上限配置

let mcs = SJMediaCacheServer.shared

// 缓存资源个数上限, 0 表示不限制
mcs.cacheCountLimit = 100

// 资源在缓存中保留的最长时间(秒), 0 表示不过期
mcs.maxDiskAgeForCache = 7 * 24 * 60 * 60

// 磁盘缓存最大占用(字节), 0 表示不限制
mcs.maxDiskSizeForCache = 1024 * 1024 * 1024

// 设备预留的最小可用磁盘空间(字节), 低于该值时会清理部分缓存
mcs.reservedFreeDiskSpace = 200 * 1024 * 1024

缓存查询与清理

let mcs = SJMediaCacheServer.shared

// 资源是否已完整缓存
let stored = mcs.isStored(for: url)

// 可清理缓存占用大小(不含受保护缓存)
let removableBytes = mcs.countOfBytesRemovableCaches

// 删除指定资源缓存(受保护缓存不会被删除), 返回是否删除成功
let removed = mcs.removeCache(for: url)

// 删除所有未受保护的缓存
mcs.removeAllRemovableCaches()

预加载(Prefetch)

let mcs = SJMediaCacheServer.shared

// 控制最大并发预加载任务数, 默认 1
mcs.maxConcurrentPrefetchCount = 2

// 1) 预加载整个资源
let task1 = mcs.prefetch(with: url, progress: { progress in
    print("prefetch progress: \(progress)")
}, completed: { error in
    print("prefetch finished, error: \(String(describing: error))")
})

// 2) 按字节大小预加载(以低优先级下载)
let task2 = mcs.prefetch(with: url, preloadSize: 5 * 1024 * 1024)

// 3) 按文件个数预加载(适合 HLS 提前缓存若干 ts 分片)
let task3 = mcs.prefetch(with: url, numberOfPreloadedFiles: 3, progress: { progress in
    print("HLS prefetch progress: \(progress)")
}, completed: { error in
    print("HLS prefetch finished, error: \(String(describing: error))")
})

// 取消单个任务
task1?.cancel()

// 取消所有排队与执行中的预加载任务
mcs.cancelAllPrefetchTasks()

资源导出(Export)

导出用于把资源生成离线可用的独立缓存,与播放/预加载缓存分开管理。删除导出资源需通过导出相关接口。

let mcs = SJMediaCacheServer.shared

// 控制最大并发导出任务数, 默认 1
mcs.maxConcurrentExportCount = 1

// 获取(或创建)导出器, 并立即开始导出
let exporter = mcs.exportAsset(with: url, resumes: true)

// 查询状态与进度
let status: MCSAssetExportStatus = mcs.exportStatus(with: url) // waiting / exporting / finished / failed / suspended / cancelled
let progress: Float = mcs.exportProgress(with: url)

// 所有导出资源占用大小
let exportedBytes = mcs.countOfBytesAllExportedAssets

// 把播放产生的缓存同步进度到导出器
mcs.synchronizeForExporter(withAssetURL: url)

// 删除单个 / 全部导出资源
mcs.removeExportAsset(with: url)
mcs.removeAllExportAssets()

如需监听导出事件,实现 MCSAssetExportObserver 并注册即可(无需手动注销,管理器会在合适时机自动移除):

mcs.registerExportObserver(observer)
mcs.removeExportObserver(observer)

自定义请求与请求头

let mcs = SJMediaCacheServer.shared

// 统一为所有下载请求追加请求头
mcs.requestHandler = { request in
    request.addValue("YourHeaderValue", forHTTPHeaderField: "YourHeaderField")
    return request
}

// 为指定资源 URL 按数据类型设置请求头
// type 可取: .HLS / .HLSPlaylist / .HLSAESKey / .HLSTs / .FILE
mcs.assetURL(url, setValue: "YourHeaderValue", forHTTPAdditionalHeaderField: "YourHeaderField", ofType: .HLS)

// 自定义 URLSessionConfiguration
mcs.customSessionConfig { config in
    config.timeoutIntervalForRequest = 15
}

数据编解码(本地缓存加密)

let mcs = SJMediaCacheServer.shared

// 写入缓存前对数据编码(加密)
mcs.writeDataEncoder = { request, offset, data in
    return encrypt(data) // 你的加密实现
}

// 读取时对数据解码
mcs.readDataDecoder = { request, offset, data in
    return decrypt(data) // 你的解密实现
}

资源标识解析

当不同 URL 指向同一资源时,可返回相同标识符让它们共用同一份缓存(默认实现会去掉 query 部分):

SJMediaCacheServer.shared.resolveAssetIdentifier = { url in
    return url.deletingPathExtension().lastPathComponent
}

失败通知与日志

// 监听播放请求失败
NotificationCenter.default.addObserver(
    forName: MCSPlayBackRequestTaskDidFailedNotification,
    object: nil,
    queue: .main
) { note in
    let url = note.userInfo?[MCSPlayBackRequestURLUserInfoKey] as? URL
    let error = note.userInfo?[MCSPlayBackRequestFailureUserInfoKey] as? Error
    print("playback request failed: \(String(describing: url)), \(String(describing: error))")
}

// 控制台日志(仅 DEBUG 生效)
SJMediaCacheServer.shared.isEnabledConsoleLog = true
SJMediaCacheServer.shared.logLevel = .debug

致谢 / 原作者

本框架由 畅三江 创建并开源,本仓库为基于其作品的 Swift 6 + Swift Package Manager 迁移版本,向原作者致以诚挚感谢。

Reference

License

SJMediaCacheServer 基于 MIT 许可证发布,详见仓库中的 LICENSE 文件。

About

Swift 6 + SPM 媒体缓存框架,基于 HTTP 代理实现 HLS 与文件媒体的边播边缓存,提升播放体验与节省流量

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages