-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathM3u8Parser.swift
executable file
·182 lines (149 loc) · 7.37 KB
/
M3u8Parser.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//
// m3u8Handler.swift
// DownLoadFile
//
// Created by yang on 2017/10/5.
// Copyright © 2017年 yang. All rights reserved.
//
import Foundation
import Alamofire
protocol M3u8ParserDelegate: class {
func parseM3u8Succeeded(by parser: M3u8Parser)
func parseM3u8Failed(by parser: M3u8Parser)
}
open class M3u8Parser {
weak var delegate: M3u8ParserDelegate?
var m3u8Data: String = ""
var tsSegmentArray = [M3u8TsSegmentModel]()
var tsPlaylist = M3u8Playlist()
var identifier = ""
var videoIndet = ""
/**
To parse m3u8 file with a provided URL.
- parameter url: A string of URL you want to parse.
*/
open func parse(with url: String) {
guard let m3u8ParserDelegate = delegate else {
//print("M3u8ParserDelegate not set.")
return
}
if !(url.hasPrefix("http://") || url.hasPrefix("https://")) {
//print("Invalid URL.")
m3u8ParserDelegate.parseM3u8Failed(by: self)
return
}
DispatchQueue.global(qos: .background).async {
do {
let m3u8Content = try String(contentsOf: URL(string: url)!, encoding: .utf8)
//print(m3u8Content)
if m3u8Content == "" {
//print("Empty m3u8 content.")
m3u8ParserDelegate.parseM3u8Failed(by: self)
return
} else {
guard (m3u8Content.range(of: "#EXTM3U") != nil) else {
//print("No EXTINF info.")
m3u8ParserDelegate.parseM3u8Failed(by: self)
return
}
self.m3u8Data = m3u8Content
if self.tsSegmentArray.count > 0 { self.tsSegmentArray.removeAll() }
let segmentRange = m3u8Content.range(of: "#EXTM3U")!
let segmentsString = String(m3u8Content.suffix(from: segmentRange.lowerBound)).components(separatedBy: "#EXT-X-ENDLIST")
// 以 \n为节点 把文件内的string转化为数组
var segmentArray = segmentsString[0].components(separatedBy: "\n")
// 筛选出不包含#EXT-X-DISCONTINUITY这个标签的数组
segmentArray = segmentArray.filter { !$0.contains("#EXT-X-DISCONTINUITY") }
//print(segmentArray)
var keyArray: [String] = []
if m3u8Content.contains("#EXT-X-KEY:") {
keyArray = segmentArray.filter{ $0.contains("#EXT-X-KEY") }
}
let duretionArray = segmentArray.filter{ $0.contains("#EXTINF") }
let tsArray = segmentArray.filter{ $0.contains(".ts?") }
//print(keyArray)
//print(duretionArray)
//print(tsArray)
for i in 0..<tsArray.count {
var segmentModel = M3u8TsSegmentModel()
if m3u8Content.contains("#EXT-X-KEY:") {
/// #EXT-X-KEY:METHOD=AES-128,URI="http://eduwind.cn/hls/clef?id=35631",IV=0x88e1ae3cd5464adfcc0e267426e2b814
// 类似于上边的字符串,要把URI中的替换掉
var keys = keyArray[i]
//print(keys)
// 从第一个字母开始往后数11位获得下标
let startSlicingIndex = keys.index(keys.startIndex, offsetBy: 11)
// key之后的所有 ,获得子字符串
let subvalues = keys[startSlicingIndex...]
// 以 , 为分界点,分为数组
let keyArray = subvalues.components(separatedBy: ",")
// 获取下标为1的字符串,也就是URI 的字符串
var URIkey = keyArray[1]
// 截取URI的5位后的字符串,得到的是http://eduwind.cn/hls/clef?id=35631",多一个引号,需要去掉
let URIStarIndex = URIkey.index(URIkey.startIndex, offsetBy: 5)
let URIEndIndex = URIkey[URIStarIndex...].index(URIkey[URIStarIndex...].endIndex, offsetBy: -2)
let keyurl = URIkey[URIStarIndex...][...URIEndIndex]
// print(keyurl)
let starIndex = keys.index(keys.startIndex, offsetBy: 31)
keys.replaceSubrange(starIndex...keys.index(starIndex, offsetBy: keyurl.count - 1), with: "http://localhost:8099/videoKey.txt")
//print(keys)
segmentModel.key = keys
requextVideoKey(keyUrl: String(keyurl))
}
let segmentDurationPart = duretionArray[i].components(separatedBy: ":")[1]
//print(duretionArray[i].components(separatedBy: ":"))
//print(segmentDurationPart)
var segmentDuration: Float = 0.0
if segmentDurationPart.contains(",") {
// 然后以 , 为节点转化为数组取下首元素
segmentDuration = Float(segmentDurationPart.components(separatedBy: ",")[0])!
// //print(segmentArray)
} else {
segmentDuration = Float(segmentDurationPart)!
}
segmentModel.duration = segmentDuration
let segmentURL = tsArray[i]
//print(segmentURL)
if m3u8Content.contains("#EXT-X-KEY:") {
segmentModel.locationURL = "http://u20094.cloud.eduwind.com" + segmentURL
}else {
segmentModel.locationURL = segmentURL
}
// segmentModel.key = ""
self.tsSegmentArray.append(segmentModel)
//print(self.tsSegmentArray)
}
self.tsPlaylist.initSegment(with: self.tsSegmentArray)
self.tsPlaylist.identifier = self.identifier
self.tsPlaylist.videoIndet = self.videoIndet
m3u8ParserDelegate.parseM3u8Succeeded(by: self)
}
} catch let error {
//print(error.localizedDescription)
//print("Read m3u8 file content error.")
}
}
func requextVideoKey(keyUrl: String) {
// 请求key的操作
let url = URL(string: String(keyUrl))
var request = URLRequest(url: url!) //请求
request.httpMethod = "GET" //修改http方法
let session = URLSession.shared
let dataTask = session.dataTask(with: request,
completionHandler: {(data, response, error) -> Void in
if error != nil{
//print(error.debugDescription)
}else{
// let string = String(data: data!, encoding: String.Encoding.utf8)
// print(string!)
let myDirectory = NSHomeDirectory() + "/Documents/Downloads/\(self.videoIndet)/\(self.identifier)/videoKey.txt"
let fileManager = FileManager.default
// 将key 写入文件
fileManager.createFile(atPath: myDirectory, contents: data, attributes: nil)
}
}) as URLSessionTask
//使用resume方法启动任务
dataTask.resume()
}
}
}