Skip to content

Commit 9d17405

Browse files
committed
Extract manifest processing to separate class, small fixes from pairing
1 parent 8d94576 commit 9d17405

File tree

3 files changed

+187
-25
lines changed

3 files changed

+187
-25
lines changed

Examples/MuxPlayerSwiftExample/MuxPlayerSwiftExample/ExperimentalCacheViewController.swift

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,22 @@ import MuxPlayerSwift
1010

1111
class ExperimentalCacheViewController: UIViewController {
1212

13-
var playbackID: String = "qxb01i6T202018GFS02vp9RIe01icTcDCjVzQpmaB00CUisJ4"
13+
var playbackID: String = "a4nOgmxGWg6gULfcBbAa00gXyfcwPnAFldF8RdsNyk8M"
1414

1515
lazy var topPlayerViewController = AVPlayerViewController(
16-
playbackID: playbackID
16+
playbackID: playbackID,
17+
playbackOptions: PlaybackOptions.init(
18+
maximumResolutionTier: .upTo720p,
19+
minimumResolutionTier: .atLeast720p
20+
)
1721
)
1822

1923
lazy var bottomPlayerViewController = AVPlayerViewController(
20-
playbackID: playbackID
24+
playbackID: playbackID,
25+
playbackOptions: PlaybackOptions.init(
26+
maximumResolutionTier: .upTo720p,
27+
minimumResolutionTier: .atLeast720p
28+
)
2129
)
2230

2331
override func viewDidLoad() {
@@ -72,20 +80,47 @@ class ExperimentalCacheViewController: UIViewController {
7280
multiplier: 0.5
7381
),
7482
])
83+
84+
let recreatePlayersNavigationBarButtonItem = UIBarButtonItem(
85+
title: "Recreate Players",
86+
image: nil,
87+
primaryAction: UIAction(
88+
handler: { _ in
89+
self.recreatePlayerInstances()
90+
}
91+
)
92+
)
93+
94+
let configureAlternativePlaybackIDNavigationBarButtonItem = UIBarButtonItem(
95+
title: "Configure Alternative Playback ID",
96+
image: nil,
97+
primaryAction: UIAction(
98+
handler: { _ in
99+
self.configureAlternativePlaybackID()
100+
}
101+
)
102+
)
103+
navigationController?.navigationItem.rightBarButtonItems = [
104+
recreatePlayersNavigationBarButtonItem,
105+
configureAlternativePlaybackIDNavigationBarButtonItem
106+
]
75107
}
76108

77109
override func viewDidAppear(_ animated: Bool) {
78110
super.viewDidAppear(animated)
79111

80112
startObservingPlayerAccessLog()
81113

82-
self.topPlayerViewController.player?.play()
114+
// self.topPlayerViewController.player?.play()
115+
116+
self.topPlayerViewController.player?.isMuted = true
117+
self.bottomPlayerViewController.player?.isMuted = true
83118

84119
DispatchQueue.main.asyncAfter(
85120
deadline: .now() + 20,
86121
execute: DispatchWorkItem(
87122
block: {
88-
self.bottomPlayerViewController.player?.play()
123+
// self.bottomPlayerViewController.player?.play()
89124
}
90125
)
91126
)
@@ -101,6 +136,27 @@ class ExperimentalCacheViewController: UIViewController {
101136
super.viewWillDisappear(animated)
102137
}
103138

139+
func recreatePlayerInstances() {
140+
self.topPlayerViewController = AVPlayerViewController(
141+
playbackID: playbackID,
142+
playbackOptions: PlaybackOptions.init(
143+
maximumResolutionTier: .upTo720p,
144+
minimumResolutionTier: .atLeast720p
145+
)
146+
)
147+
148+
self.bottomPlayerViewController = AVPlayerViewController(
149+
playbackID: playbackID,
150+
playbackOptions: PlaybackOptions.init(
151+
maximumResolutionTier: .upTo720p,
152+
minimumResolutionTier: .atLeast720p
153+
)
154+
)
155+
}
156+
157+
func configureAlternativePlaybackID() {
158+
159+
}
104160

105161
//MARK: Player ABR Observation
106162

@@ -149,6 +205,6 @@ class ExperimentalCacheViewController: UIViewController {
149205

150206
print("\(#function) Observed Bitrate Standard Deviation: \(lastEvent.observedBitrateStandardDeviation)")
151207

152-
print("\(#function) URI: \(String(describing: lastEvent.uri))")
208+
print("ABR \(#function) URI: \(String(describing: lastEvent.uri))")
153209
}
154210
}

Sources/MuxPlayerSwift/PublicAPI/Extensions/AVPlayerViewController+Mux.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ extension AVPlayerViewController {
6565
/// you'd like to play
6666
/// - playbackOptions: playback-related options such
6767
/// as custom domain and maximum resolution
68-
convenience init(
68+
public convenience init(
6969
playbackID: String,
7070
playbackOptions: PlaybackOptions
7171
) {

Sources/MuxPlayerSwift/ReverseProxyServer/ReverseProxyServer.swift

Lines changed: 124 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,137 @@ import GCDWebServer
99

1010
class ReverseProxyServer {
1111

12-
struct Event {
12+
struct Event: CustomStringConvertible {
1313

1414
enum Kind {
1515
case manifestRequestReceived
1616
case segmentRequestReceived
17-
case segmentCacheMiss(key: URLRequest)
18-
case segmentCacheHit(key: URLRequest)
19-
case segmentCacheStored(key: URLRequest)
17+
case segmentCacheMiss(
18+
key: URLRequest
19+
)
20+
case segmentCacheHit(
21+
key: URLRequest
22+
)
23+
case segmentCacheStored(
24+
key: URLRequest,
25+
cacheDiskUsageInBytes: Int,
26+
segmentSizeInBytes: Int
27+
)
2028
}
2129

2230
let originURL: URL
2331

2432
let kind: Kind
2533

34+
var description: String {
35+
switch kind {
36+
case .manifestRequestReceived:
37+
return "Manifest Request - Origin URL: \(originURL.absoluteString)"
38+
case .segmentRequestReceived:
39+
return "Segment Request Received - Origin URL: \(originURL.absoluteString)"
40+
case .segmentCacheMiss(key: let key):
41+
return "Segment Cache Miss - Key: \(key) Origin URL: \(originURL.absoluteString)"
42+
case .segmentCacheHit(key: let key):
43+
return "Segment Cache Hit - Key: \(key) Origin URL: \(originURL.absoluteString)"
44+
case .segmentCacheStored(key: let key, cacheDiskUsageInBytes: let cacheDiskUsageInBytes, segmentSizeInBytes: let segmentSizeInBytes):
45+
return "Segment Cache Stored - Key: \(key) CacheDiskUsageInBytes: \(cacheDiskUsageInBytes) SegmentSizeInBytes: \(segmentSizeInBytes) Origin URL: \(originURL.absoluteString)"
46+
}
47+
}
48+
2649
}
2750

2851
class EventRecorder {
2952

3053
func didRecord(event: Event) {
54+
print("RPS - \(Date()) - \(event.description)")
55+
}
3156

32-
switch event.kind {
33-
case .manifestRequestReceived:
34-
print("RPS Manifest Request: \(event.originURL.absoluteString)")
35-
case .segmentRequestReceived:
36-
print("RPS Segment Request: \(event.originURL.absoluteString)")
37-
case .segmentCacheMiss(key: let key):
38-
print("RPS Cache Miss Key: \(key) Origin: \(event.originURL.absoluteString)")
39-
case .segmentCacheHit(key: let key):
40-
print("RPS Cache Hit Key: \(key) Origin: \(event.originURL.absoluteString)")
41-
case .segmentCacheStored(key: let key):
42-
print("RPS Cache Stored Key: \(key) Origin: \(event.originURL.absoluteString)")
57+
}
58+
59+
class ManifestReversifier {
60+
let port: UInt = 1234
61+
let originURLKey: String = "__hls_origin_url"
62+
63+
func reversifyManifest(
64+
encodedManifest: Data,
65+
manifestOriginURL: URL
66+
) {
67+
let originalManifest = String(
68+
data: encodedManifest,
69+
encoding: .utf8
70+
)
71+
72+
let parsedManifest = originalManifest?
73+
.components(separatedBy: .newlines)
74+
.map { line in self.processPlaylistLine(line, forOriginURL: manifestOriginURL) }
75+
.joined(separator: "\n")
76+
}
77+
78+
func processPlaylistLine(
79+
_ line: String,
80+
forOriginURL originURL: URL
81+
) -> String {
82+
guard !line.isEmpty else { return line }
83+
84+
if line.hasPrefix("#") {
85+
return lineByReplacingURI(line: line, forOriginURL: originURL)
4386
}
4487

88+
if let originalSegmentURL = absoluteURL(from: line, forOriginURL: originURL),
89+
let reverseProxyURL = reverseProxyURL(from: originalSegmentURL) {
90+
return reverseProxyURL.absoluteString
91+
}
92+
return line
93+
}
94+
95+
func lineByReplacingURI(
96+
line: String,
97+
forOriginURL originURL: URL
98+
) -> String {
99+
let uriPattern = try! NSRegularExpression(pattern: "URI=\"([^\"]*)\"")
100+
let lineRange = NSRange(location: 0, length: line.count)
101+
guard let result = uriPattern.firstMatch(in: line, options: [], range: lineRange) else { return line }
102+
103+
let uri = (line as NSString).substring(with: result.range(at: 1))
104+
guard let absoluteURL = absoluteURL(from: uri, forOriginURL: originURL) else { return line }
105+
guard let reverseProxyURL = reverseProxyURL(from: absoluteURL) else { return line }
106+
107+
return uriPattern.stringByReplacingMatches(in: line, options: [], range: lineRange, withTemplate: "URI=\"\(reverseProxyURL.absoluteString)\"")
45108
}
46109

110+
func absoluteURL(from line: String, forOriginURL originURL: URL) -> URL? {
111+
if line.hasPrefix("http://") || line.hasPrefix("https://") {
112+
return URL(string: line)
113+
}
114+
115+
guard let scheme = originURL.scheme,
116+
let host = originURL.host
117+
else {
118+
print("Error: bad url")
119+
return nil
120+
}
121+
122+
let path: String
123+
if line.hasPrefix("/") {
124+
path = line
125+
} else {
126+
path = originURL.deletingLastPathComponent().appendingPathComponent(line).path
127+
}
128+
129+
return URL(string: scheme + "://" + host + path)?.standardized
130+
}
131+
132+
func reverseProxyURL(from originURL: URL) -> URL? {
133+
guard var components = URLComponents(url: originURL, resolvingAgainstBaseURL: false) else { return nil }
134+
components.scheme = "http"
135+
components.host = "127.0.0.1"
136+
components.port = Int(port)
137+
138+
let originURLQueryItem = URLQueryItem(name: originURLKey, value: originURL.absoluteString)
139+
components.queryItems = (components.queryItems ?? []) + [originURLQueryItem]
140+
141+
return components.url
142+
}
47143
}
48144

49145
var session: URLSession = .shared
@@ -52,16 +148,19 @@ class ReverseProxyServer {
52148
var segmentCache: URLCache
53149

54150
var eventRecorder: EventRecorder = EventRecorder()
151+
var manifestReversifier: ManifestReversifier = ManifestReversifier()
55152

56-
private let port: UInt = 1234
153+
let port: UInt = 1234
57154
let originURLKey: String = "__hls_origin_url"
58155

156+
let defaultDiskCapacity = 256_000_000
157+
59158
init() {
60159
self.webServer = GCDWebServer()
61160

62161
self.segmentCache = URLCache(
63162
memoryCapacity: 0,
64-
diskCapacity: 10_000_000
163+
diskCapacity: defaultDiskCapacity
65164
)
66165
}
67166

@@ -240,6 +339,7 @@ class ReverseProxyServer {
240339
let task = self.session.dataTask(
241340
with: reverseProxyRequest
242341
) { data, response, error in
342+
243343
guard let data = data, let response = response else {
244344
return completion(GCDWebServerErrorResponse(statusCode: 500))
245345
}
@@ -262,11 +362,17 @@ class ReverseProxyServer {
262362
for: strippedRequest
263363
)
264364

365+
let segmentSizeInBytes = data.count
366+
367+
let cacheDiskUsageInBytes = self.segmentCache.currentDiskUsage
368+
265369
self.eventRecorder.didRecord(
266370
event: Event(
267371
originURL: originURL,
268372
kind: .segmentCacheStored(
269-
key: strippedRequest
373+
key: strippedRequest,
374+
cacheDiskUsageInBytes: cacheDiskUsageInBytes,
375+
segmentSizeInBytes: segmentSizeInBytes
270376
)
271377
)
272378
)

0 commit comments

Comments
 (0)