Skip to content

Commit

Permalink
Add HTTP Response StatusCode List and fix potential race condition
Browse files Browse the repository at this point in the history
  • Loading branch information
stonerl committed May 17, 2024
1 parent 6eba2a4 commit c3e4c07
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 49 deletions.
102 changes: 59 additions & 43 deletions Model/Player/PlayerStreams.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,41 +57,54 @@ extension PlayerModel {
}

func streamsWithInstance(instance _: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) {
let forbiddenAssetTestGroup = DispatchGroup()
var hasForbiddenAsset = false
// Queue for stream processing
let streamProcessingQueue = DispatchQueue(label: "stream.yattee.streamProcessing.Queue", qos: .userInitiated)
// Queue for accessing the processedStreams array
let processedStreamsQueue = DispatchQueue(label: "stream.yattee.processedStreams.Queue")
// DispatchGroup for managing multiple tasks
let streamProcessingGroup = DispatchGroup()

let (nonHLSAssets, hlsURLs) = getAssets(from: streams)
var processedStreams = [Stream]()

if let randomStream = nonHLSAssets.randomElement() {
let instance = randomStream.0
let asset = randomStream.1
let url = randomStream.2
let requestRange = randomStream.3
for stream in streams {
streamProcessingQueue.async(group: streamProcessingGroup) {
let forbiddenAssetTestGroup = DispatchGroup()
var hasForbiddenAsset = false

let (nonHLSAssets, hlsURLs) = self.getAssets(from: [stream])

if let randomStream = nonHLSAssets.randomElement() {
let instance = randomStream.0
let asset = randomStream.1
let url = randomStream.2
let requestRange = randomStream.3

// swiftlint:disable:next shorthand_optional_binding
if let asset = asset, let instance = instance, !instance.proxiesVideos {
if instance.app == .invidious {
self.testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
hasForbiddenAsset = isForbidden
}
} else if instance.app == .piped {
self.testPipedAssets(asset: asset, requestRange: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
hasForbiddenAsset = isForbidden
}
}
}
} else if let randomHLS = hlsURLs.randomElement() {
let instance = randomHLS.0
let asset = AVURLAsset(url: randomHLS.1)

if let asset = asset, let instance = instance, !instance.proxiesVideos {
if instance.app == .invidious {
testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
hasForbiddenAsset = isForbidden
if instance?.app == .piped {
self.testPipedAssets(asset: asset, requestRange: nil, isHLS: true, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
hasForbiddenAsset = isForbidden
}
}
} else if instance.app == .piped {
testPipedAssets(asset: asset, requestRange: requestRange!, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: { isForbidden in
hasForbiddenAsset = isForbidden
})
}
}
} else if let randomHLS = hlsURLs.randomElement() {
let instance = randomHLS.0
let asset = AVURLAsset(url: randomHLS.1)

if instance?.app == .piped {
testPipedAssets(asset: asset, requestRange: nil, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: { isForbidden in
hasForbiddenAsset = isForbidden
})
}
}

forbiddenAssetTestGroup.notify(queue: .main) {
let processedStreams = streams.map { stream -> Stream in
forbiddenAssetTestGroup.wait()

// Post-processing code
if let instance = stream.instance {
if instance.app == .invidious {
if hasForbiddenAsset || instance.proxiesVideos {
Expand All @@ -104,35 +117,36 @@ extension PlayerModel {
}
} else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset {
if let hlsURL = stream.hlsURL {
forbiddenAssetTestGroup.enter()
PipedAPI.nonProxiedAsset(url: hlsURL) { nonProxiedURL in
if let nonProxiedURL = nonProxiedURL {
PipedAPI.nonProxiedAsset(url: hlsURL) { possibleNonProxiedURL in
if let nonProxiedURL = possibleNonProxiedURL {
stream.hlsURL = nonProxiedURL.url
}
forbiddenAssetTestGroup.leave()
}
} else {
if let audio = stream.audioAsset {
forbiddenAssetTestGroup.enter()
PipedAPI.nonProxiedAsset(asset: audio) { nonProxiedAudioAsset in
stream.audioAsset = nonProxiedAudioAsset
forbiddenAssetTestGroup.leave()
}
}
if let video = stream.videoAsset {
forbiddenAssetTestGroup.enter()
PipedAPI.nonProxiedAsset(asset: video) { nonProxiedVideoAsset in
stream.videoAsset = nonProxiedVideoAsset
forbiddenAssetTestGroup.leave()
}
}
}
}
}
return stream

// Append to processedStreams within the processedStreamsQueue
processedStreamsQueue.sync {
processedStreams.append(stream)
}
}
}

forbiddenAssetTestGroup.notify(queue: .main) {
streamProcessingGroup.notify(queue: .main) {
// Access and pass processedStreams within the processedStreamsQueue block
processedStreamsQueue.sync {
completion(processedStreams)
}
}
Expand Down Expand Up @@ -161,21 +175,23 @@ extension PlayerModel {
}

private func testAsset(url: URL, range: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
// In case the range is nil, generate a random one.
let randomEnd = Int.random(in: 200 ... 800)
let requestRange = range ?? "0-\(randomEnd)"
let HTTPStatusForbidden = 403

forbiddenAssetTestGroup.enter()
URLTester.testURLResponse(url: url, range: requestRange, isHLS: isHLS) { statusCode in
completion(statusCode == HTTPStatusForbidden)
completion(statusCode == HTTPStatus.Forbidden)
forbiddenAssetTestGroup.leave()
}
}

private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
PipedAPI.nonProxiedAsset(asset: asset) { nonProxiedAsset in
if let nonProxiedAsset = nonProxiedAsset {
PipedAPI.nonProxiedAsset(asset: asset) { possibleNonProxiedAsset in
if let nonProxiedAsset = possibleNonProxiedAsset {
self.testAsset(url: nonProxiedAsset.url, range: requestRange, isHLS: isHLS, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: completion)
} else {
completion(false)
}
}
}
Expand Down
81 changes: 81 additions & 0 deletions Shared/HTTPStatus.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// HTTP response status codes

enum HTTPStatus {
// Informational responses (100 - 199)

static let Continue = 100
static let SwitchingProtocols = 101
static let Processing = 102
static let EarlyHints = 103

// Successful responses (200 - 299)

static let OK = 200
static let Created = 201
static let Accepted = 202
static let NonAuthoritativeInformation = 203
static let NoContent = 204
static let ResetContent = 205
static let PartialContent = 206
static let MultiStatus = 207
static let AlreadyReported = 208
static let IMUsed = 226

// Redirection messages (300 - 399)

static let MultipleChoices = 300
static let MovedPermanently = 301
static let Found = 302
static let SeeOther = 303
static let NotModified = 304
static let UseProxy = 305
static let SwitchProxy = 306
static let TemporaryRedirect = 307
static let PermanentRedirect = 308

// Client error responses (400 - 499)

static let BadRequest = 400
static let Unauthorized = 401
static let PaymentRequired = 402
static let Forbidden = 403
static let NotFound = 404
static let MethodNotAllowed = 405
static let NotAcceptable = 406
static let ProxyAuthenticationRequired = 407
static let RequestTimeout = 408
static let Conflict = 409
static let Gone = 410
static let LengthRequired = 411
static let PreconditionFailed = 412
static let PayloadTooLarge = 413
static let URITooLong = 414
static let UnsupportedMediaType = 415
static let RangeNotSatisfiable = 416
static let ExpectationFailed = 417
static let IAmATeapot = 418
static let MisdirectedRequest = 421
static let UnprocessableEntity = 422
static let Locked = 423
static let FailedDependency = 424
static let TooEarly = 425
static let UpgradeRequired = 426
static let PreconditionRequired = 428
static let TooManyRequests = 429
static let RequestHeaderFieldsTooLarge = 431
static let UnavailableForLegalReasons = 451

// Server error responses (500 - 599)

static let InternalServerError = 500
static let NotImplemented = 501
static let BadGateway = 502
static let ServiceUnavailable = 503
static let GatewayTimeout = 504
static let HTTPVersionNotSupported = 505
static let VariantAlsoNegotiates = 506
static let InsufficientStorage = 507
static let LoopDetected = 508
static let NotExtended = 510
static let NetworkAuthenticationRequired = 511
}
18 changes: 12 additions & 6 deletions Shared/URLTester.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ enum URLTester {
private static let hlsMediaPrefix = "#EXT-X-MEDIA:"
private static let hlsInfPrefix = "#EXTINF:"
private static let uriRegex = "(?<=URI=\")(.*?)(?=\")"
private static let HTTPStatusForbidden = 403

static func testURLResponse(url: URL, range: String, isHLS: Bool, completion: @escaping (Int) -> Void) {
if isHLS {
Expand All @@ -24,7 +23,7 @@ enum URLTester {

var dataTask: URLSessionDataTask?
dataTask = URLSession.shared.dataTask(with: request) { _, response, _ in
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? HTTPStatusForbidden
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? HTTPStatus.Forbidden
Logger(label: "stream.yattee.httpRequest").info("URL: \(url) | Status Code: \(statusCode)")
completion(statusCode, dataTask)
}
Expand All @@ -37,6 +36,8 @@ enum URLTester {
httpRequest(url: url, range: range) { statusCode, _ in
completion(statusCode)
}
} else {
completion(HTTPStatus.NotFound)
}
}
}
Expand Down Expand Up @@ -64,10 +65,15 @@ enum URLTester {

private static func parseHLSManifest(manifestUrl: URL, completion: @escaping ([URL]) -> Void) {
URLSession.shared.dataTask(with: manifestUrl) { data, _, _ in
guard let data = data,
let manifest = String(data: data, encoding: .utf8),
!manifest.isEmpty
else {
// swiftlint:disable:next shorthand_optional_binding
guard let data = data else {
Logger(label: "stream.yattee.httpRequest").error("Data is nil")
completion([])
return
}

// swiftlint:disable:next non_optional_string_data_conversion
guard let manifest = String(data: data, encoding: .utf8), !manifest.isEmpty else {
Logger(label: "stream.yattee.httpRequest").error("Cannot read or empty HLS manifest")
completion([])
return
Expand Down
8 changes: 8 additions & 0 deletions Yattee.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,9 @@
37FFC440272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
37FFC441272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
37FFC442272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
E25028B02BF790F5002CB9FC /* HTTPStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */; };
E25028B12BF790F5002CB9FC /* HTTPStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */; };
E25028B22BF790F5002CB9FC /* HTTPStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */; };
E258F38A2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
E258F38B2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
E258F38C2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
Expand Down Expand Up @@ -1542,6 +1545,7 @@
3DA101AD287C30F50027D920 /* DEVELOPMENT_TEAM.template.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.template.xcconfig; sourceTree = "<group>"; };
3DA101AE287C30F50027D920 /* DEVELOPMENT_TEAM.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.xcconfig; sourceTree = "<group>"; };
3DA101AF287C30F50027D920 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = "<group>"; };
E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPStatus.swift; sourceTree = "<group>"; };
E258F3892BF61BD2005B8C28 /* URLTester.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLTester.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -2290,6 +2294,7 @@
372915E52687E3B900F5A35B /* Defaults.swift */,
37D2E0D328B67EFC00F64D52 /* Delay.swift */,
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */,
E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */,
375B537828DF6CBB004C1D19 /* Localizable.strings */,
3729037D2739E47400EA99F6 /* MenuCommands.swift */,
37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */,
Expand Down Expand Up @@ -3120,6 +3125,7 @@
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
37F7D82C289EB05F00E2B3D0 /* SettingsPickerModifier.swift in Sources */,
375EC95D289EEEE000751258 /* QualityProfile.swift in Sources */,
E25028B02BF790F5002CB9FC /* HTTPStatus.swift in Sources */,
3773B8102ADC076800B5FEF3 /* ScrollViewMatcher.swift in Sources */,
371B7E662759786B00D21217 /* Comment+Fixtures.swift in Sources */,
37BE0BD326A1D4780092E2DB /* AppleAVPlayerView.swift in Sources */,
Expand Down Expand Up @@ -3593,6 +3599,7 @@
37D4B19826717E1500C925CA /* Video.swift in Sources */,
371B7E5D27596B8400D21217 /* Comment.swift in Sources */,
37EF5C232739D37B00B03725 /* MenuModel.swift in Sources */,
E25028B12BF790F5002CB9FC /* HTTPStatus.swift in Sources */,
37A7D71C2B680E66009CB1ED /* LocationsSettingsGroupExporter.swift in Sources */,
37BC50A92778A84700510953 /* HistorySettings.swift in Sources */,
374DE88128BB896C0062BBF2 /* PlayerDragGesture.swift in Sources */,
Expand Down Expand Up @@ -3873,6 +3880,7 @@
37130A61277657300033018A /* PersistenceController.swift in Sources */,
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
3717407F2949D40800FDDBC7 /* ChannelLinkView.swift in Sources */,
E25028B22BF790F5002CB9FC /* HTTPStatus.swift in Sources */,
370F4FAA27CC163B001B35DC /* PlayerBackend.swift in Sources */,
376A33E62720CB35000C1D6B /* Account.swift in Sources */,
3763C98B290C7A50004D3B5F /* OpenVideosView.swift in Sources */,
Expand Down

0 comments on commit c3e4c07

Please sign in to comment.