使用 Decodable
协议对 HTTP Live Streaming 的 Master 和 Media 播放列表进行解码。
下面的示例展示了如何从提供的文本中解码一个简单的 Media 播放列表。 该类型遵循 Decodable
协议,因此可以使用 M3U8Decoder
实例进行解码。
import M3U8Decoder
struct MediaPlaylist: Decodable {
let extm3u: Bool
let ext_x_version: Int
let ext_x_targetduration: Int
let ext_x_media_sequence: Int
let segments: [MediaSegment]
let comments: [String]
}
let m3u8 = """
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:10
# Created with Unified Streaming Platform
#EXT-X-MEDIA-SEQUENCE:2680
#EXTINF:13.333,Sample artist - Sample title
http://example.com/low.m3u8
"""
let playlist = try M3U8Decoder().decode(MediaPlaylist.self, from: m3u8)
print(playlist)
其中
segments
属性包含 MediaSegment
结构体的数组,其中包含所有已解析的媒体片段标签和 URL,例如:#EXTINF
、#EXT-X-BYTERANGE
、#EXT-X-DISCONTINUITY
、#EXT-X-KEY
、#EXT-X-MAP
、#EXT-X-PROGRAM-DATE-TIME
、#EXT-X-DATERANGE
(有关更多信息,请参见 预定义类型)comments
属性包含所有以 #
开头的行。打印
MediaPlaylist(
extm3u: true,
ext_x_version: 7,
ext_x_targetduration: 10,
ext_x_media_sequence: 2680,
segments: [
M3U8Decoder.MediaSegment(
extinf: M3U8Decoder.EXTINF(
duration: 13.333,
title: Optional("Sample artist - Sample title")
),
ext_x_byterange: nil,
ext_x_discontinuity: nil,
ext_x_key: nil,
ext_x_map: nil,
ext_x_program_date_time: nil,
ext_x_daterange: nil,
uri: "http://example.com/low.m3u8"
)
],
comments: ["Created with Unified Streaming Platform"]
)
M3U8Decoder
还可以同步和异步(async/await
)地从 Data
和 URL
实例进行解码。 例如,通过 URL 解码 Master 播放列表
import M3U8Decoder
struct MasterPlaylist: Decodable {
let extm3u: Bool
let ext_x_version: Int
let ext_x_independent_segments: Bool
let ext_x_media: [EXT_X_MEDIA]
let ext_x_i_frame_stream_inf: [EXT_X_I_FRAME_STREAM_INF]
let streams: [VariantStream]
}
let url = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8")!
let playlist = try M3U8Decoder().decode(MasterPlaylist.self, from: url)
print(playlist)
其中
streams
属性包含 VariantStream
结构体的数组,其中包含已解析的 #EXT-X-STREAM-INF
标签和 URL(有关更多信息,请参见 预定义类型)打印
MasterPlaylist(
extm3u: true,
ext_x_version: 6,
ext_x_independent_segments: true,
ext_x_media: [
M3U8Decoder.EXT_X_MEDIA(
type: "AUDIO",
group_id: "aud1",
name: "English",
language: Optional("en"),
assoc_language: nil,
autoselect: Optional(true),
default: Optional(true),
instream_id: nil,
channels: Optional("2"),
forced: nil,
uri: Optional("a1/prog_index.m3u8"),
characteristics: nil
),
...
],
ext_x_i_frame_stream_inf: [
M3U8Decoder.EXT_X_I_FRAME_STREAM_INF(
bandwidth: 187492,
average_bandwidth: Optional(183689),
codecs: ["avc1.64002a"],
resolution: Optional(M3U8Decoder.RESOLUTION(width: 1920, height: 1080)),
hdcp_level: nil,
video: nil,
uri: "v7/iframe_index.m3u8"
),
...
],
streams: [
M3U8Decoder.VariantStream(
ext_x_stream_inf: M3U8Decoder.EXT_X_STREAM_INF(
bandwidth: 2177116,
average_bandwidth: Optional(2168183),
codecs: ["avc1.640020", "mp4a.40.2"],
resolution: Optional(M3U8Decoder.RESOLUTION(width: 960, height: 540)),
frame_rate: Optional(60.0),
hdcp_level: nil,
audio: Optional("aud1"),
video: nil,
subtitles: Optional("sub1"),
closed_captions: Optional("cc1")
),
uri: "v5/prog_index.m3u8"
),
...
]
)
用于在解码之前自动更改键值的策略。
这是将播放列表标签和属性名称转换为蛇形命名法的**默认**策略。
_
替换所有 -
。例如:#EXT-X-TARGETDURATION
变为 ext_x_targetduration
。
将播放列表标签和属性名称转换为驼峰命名法。
-
后面的单词的首字母大写。-
。例如:#EXT-X-TARGETDURATION
变为 extXTargetduration
。
struct Media: Decodable {
let type: String
let groupId: String
let name: String
let language: String
let instreamId: String
}
struct MasterPlaylist: Decodable {
let extm3u: Bool
let extXVersion: Int
let extXIndependentSegments: Bool
let extXMedia: [Media]
}
let m3u8 = """
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cc",NAME="SERVICE1",LANGUAGE="en",INSTREAM-ID="SERVICE1"
"""
let decoder = M3U8Decoder()
decoder.keyDecodingStrategy = .camelCase
let playlist = try decoder.decode(MasterPlaylist.self, from: m3u8)
print(playlist)
打印
MasterPlaylist(
extm3u: true,
extXVersion: 7,
extXIndependentSegments: true,
extXMedia: [
Media(
type: "CLOSED-CAPTIONS",
groupId: "cc",
name: "SERVICE1",
language: "en",
instreamId: "SERVICE1"
)
]
)
提供从播放列表中的标签或属性名称到提供的函数指定的键的自定义转换。
struct Media: Decodable {
let type: String
let group_id: String
let name: String
let language: String
let instream_id: String
}
struct MasterPlaylist: Decodable {
let m3u: Bool
let version: Int
let independent_segments: Bool
let media: [Media]
}
let m3u8 = """
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cc",NAME="SERVICE1",LANGUAGE="en",INSTREAM-ID="SERVICE1"
"""
let decoder = M3U8Decoder()
// `EXT-X-INDEPENDENT-SEGMENTS` becomes `independent_segments`
decoder.keyDecodingStrategy = .custom { key in
key
.lowercased()
.replacingOccurrences(of: "ext", with: "")
.replacingOccurrences(of: "-x-", with: "")
.replacingOccurrences(of: "-", with: "_")
}
let playlist = try decoder.decode(MasterPlaylist.self, from: m3u8)
print(playlist)
打印
MasterPlaylist(
m3u: true,
version: 7,
independent_segments: true,
media: [
Media(
type: "CLOSED-CAPTIONS",
group_id: "cc",
name: "SERVICE1",
language: "en",
instream_id: "SERVICE1"
)
]
)
用于解码 Data
值的策略。
从十六进制字符串(例如 0xa2c4f622...
)解码 Data
。 这是默认策略。
例如,使用 IV
属性解码 #EXT-X-KEY
标签,其中数据以十六进制字符串表示
struct MediaPlaylist: Decodable {
let extm3u: Bool
let ext_x_version: Int
let segments: [MediaSegment]
}
let m3u8 = """
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://vod.domain.com/fairplay/d1acadbf70824d178601c2e55675b3b3",IV=0X99b74007b6254e4bd1c6e03631cad15b
#EXTINF:10,
http://example.com/low.m3u8
"""
let playlist = try M3U8Decoder().decode(MediaPlaylist.self, from: m3u8)
if let iv = playlist.segments.first?.ext_x_key?.iv {
print(iv.map { $0 } )
}
// Prints: [153, 183, 64, 7, 182, 37, 78, 75, 209, 198, 224, 54, 49, 202, 209, 91]
从 Base64 编码的字符串解码 Data
struct Playlist: Decodable {
let extm3u: Bool
let ext_x_version: Int
let ext_data: Data
}
let m3u8 = """
#EXTM3U
#EXT-X-VERSION:7
#EXT-DATA:SGVsbG8gQmFzZTY0IQ==
"""
let decoder = M3U8Decoder()
decoder.dataDecodingStrategy = .base64
let playlist = try decoder.decode(Playlist.self, from: m3u8)
let text = String(data: playlist.ext_data, encoding: .utf8)!
print(text) // Prints: Hello Base64!
这里有一个预定义类型列表(带有 snakeCase
键编码策略),适用于 HTTP Live Streaming 文档中的所有 master/media 标签和属性,可用于解码播放列表。
注意:这些类型的实现您可以查看 MasterPlaylist.swift 和 MediaPlaylist.swift,但无论如何,您可以创建和使用您自己的类型来解码您的播放列表。
类型 | 标签/属性 | 描述 |
---|---|---|
EXT_X_MAP |
#EXT-X-MAP:<attribute-list> |
EXT-X-MAP 标签指定如何获取解析适用 Media Segments 所需的 Media Initialization Section。 |
EXT_X_KEY |
#EXT-X-KEY:<attribute-list> #EXT_X_SESSION_KEY:<attribute-list> |
Media Segments 可能会被加密。 EXT-X-KEY/EXT_X_SESSION_KEY 标签指定如何解密它们。 |
EXT_X_DATERANGE |
#EXT-X-DATERANGE:<attribute-list> |
EXT-X-DATERANGE 标签将日期范围(即,由起始和结束日期定义的时间范围)与一组属性值对相关联。 |
EXTINF |
#EXTINF:<duration>,[<title>] |
EXTINF 标签指定 Media Segment 的持续时间。 |
EXT_X_BYTERANGE |
#EXT-X-BYTERANGE:<n>[@<o>] BYTERANGE=<n>[@<o>] |
EXT-X-BYTERANGE 标签指示 Media Segment 是由其 URI 标识的资源的子范围。 |
EXT_X_SESSION_DATA |
#EXT-X-SESSION-DATA:<attribute-list> |
EXT-X-SESSION-DATA 标签允许在 Master 播放列表中携带任意会话数据。 |
EXT_X_START |
#EXT-X-START:<attribute-list> |
EXT-X-START 标签指示播放播放列表的首选起始点。 |
EXT_X_MEDIA |
#EXT-X-MEDIA:<attribute-list> |
EXT-X-MEDIA 标签用于关联包含相同内容的备用呈现的 Media 播放列表。 |
EXT_X_STREAM_INF |
#EXT-X-STREAM-INF:<attribute-list> |
EXT-X-STREAM-INF 标签指定一个 Variant Stream,它是一组可以组合在一起播放 presentation 的 Renditions。 |
EXT_X_I_FRAME_STREAM_INF |
#EXT-X-I-FRAME-STREAM-INF:<attribute-list> |
EXT-X-I-FRAME-STREAM-INF 标签标识包含多媒体 presentation 的 I 帧的 Media 播放列表文件。 |
RESOLUTION |
RESOLUTION=<width>x<height> |
该值是一个十进制分辨率,描述了显示 Variant Stream 中所有视频的最佳像素分辨率。 |
[String] |
CODECS="codec1,codec2,..." |
该值是一个带引号的字符串,包含一个逗号分隔的格式列表,其中每个格式指定 Variant Stream 指定的一个或多个 Renditions 中存在的媒体样本类型。 |
MediaSegment |
#EXTINF #EXT-X-BYTERANGE #EXT-X-DISCONTINUITY #EXT-X-KEY #EXT-X-MAP #EXT-X-PROGRAM-DATE-TIME #EXT-X-DATERANGE <URI> |
指定一个 Media Segment。 |
VariantStream |
#EXT-X-STREAM-INF <URI> |
指定一个 Variant Stream。 |
您可以为自定义标签或属性指定带有任何键解码策略的类型,以解码您的非标准播放列表
let m3u8 = """
#EXTM3U
#EXT-CUSTOM-TAG1:1
#EXT-CUSTOM-TAG2:VALUE1=1,VALUE2="Text"
#EXT-CUSTOM-ARRAY:1
#EXT-CUSTOM-ARRAY:2
#EXT-CUSTOM-ARRAY:3
"""
struct CustomAttributes: Decodable {
let value1: Int
let value2: String
}
struct CustomPlaylist: Decodable {
let ext_custom_tag1: Int
let ext_custom_tag2: CustomAttributes
let ext_custom_array: [Int]
}
let playlist = try M3U8Decoder().decode(CustomPlaylist.self, from: m3u8)
print(playlist)
打印
CustomPlaylist(
ext_custom_tag1: 1,
ext_custom_tag2: CustomAttributes(
value1: 1,
value2: "Text"
),
ext_custom_array: [1, 2, 3]
)
M3U8Decoder
自动解析播放列表中任何标签的所有属性及其值,但是如果您使用复杂的格式(例如 json
等),则可以使用 parseHandler
回调通过您的代码解析属性
let m3u8 =
#"""
#EXTM3U
#EXT-CUSTOM-TAG:{"duration": 10.3, "title": "Title", "id": 12345}
"""#
struct CustomTag: Decodable {
let duration: Double
let title: String
let id: Int
}
struct Playlist: Decodable {
let ext_custom_tag: CustomTag
}
let decoder = M3U8Decoder()
decoder.parseHandler = { (tag: String, attributes: String) -> M3U8Decoder.ParseAction in
if tag == "EXT-CUSTOM-TAG" {
do {
if let data = attributes.data(using: .utf8) {
let dict = try JSONSerialization.jsonObject(with: data)
return .apply(dict)
}
}
catch {
print(error)
}
}
return .parse
}
let playlist = try decoder.decode(Playlist.self, from: m3u8)
print(playlist)
打印
Playlist(
ext_custom_tag: CustomTag(duration: 10.3, title: "Title", id: 12345)
)
M3U8Decoder
支持 TopLevelDecoder
协议,可以与 Combine 框架一起使用
struct MasterPlaylist: Decodable {
let extm3u: Bool
let ext_x_version: Int
let ext_x_independent_segments: Bool
let ext_x_media: [EXT_X_MEDIA]
let ext_x_i_frame_stream_inf: [EXT_X_I_FRAME_STREAM_INF]
let streams: [VariantStream]
}
let url = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8")!
let cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: MasterPlaylist.self, decoder: M3U8Decoder())
.sink (
receiveCompletion: { print($0) }, // Prints: finished
receiveValue: { playlist in
print(playlist) // Prints: MasterPlaylist(extm3u: true, ext_x_version: 6, ext_x_independent_segments: true, ext_x_media: ...
}
)
Xcode > File > Add Packages...
https://github.com/ikhvorost/M3U8Decoder.git
import M3U8Decoder
将 M3U8Decoder
包依赖项添加到您的 Package.swift
文件中
let package = Package(
...
dependencies: [
.package(url: "https://github.com/ikhvorost/M3U8Decoder.git", from: "1.0.0")
],
targets: [
.target(name: "YourPackage",
dependencies: [
.product(name: "M3U8Decoder", package: "M3U8Decoder")
]
),
]
)
M3U8Decoder
在 MIT 许可证下可用。 有关更多信息,请参见 LICENSE 文件。