mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Add HTTP Response StatusCode List and fix potential race condition
This commit is contained in:
parent
6eba2a45c8
commit
c3e4c074d6
@ -57,10 +57,21 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func streamsWithInstance(instance _: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) {
|
func streamsWithInstance(instance _: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) {
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
var processedStreams = [Stream]()
|
||||||
|
|
||||||
|
for stream in streams {
|
||||||
|
streamProcessingQueue.async(group: streamProcessingGroup) {
|
||||||
let forbiddenAssetTestGroup = DispatchGroup()
|
let forbiddenAssetTestGroup = DispatchGroup()
|
||||||
var hasForbiddenAsset = false
|
var hasForbiddenAsset = false
|
||||||
|
|
||||||
let (nonHLSAssets, hlsURLs) = getAssets(from: streams)
|
let (nonHLSAssets, hlsURLs) = self.getAssets(from: [stream])
|
||||||
|
|
||||||
if let randomStream = nonHLSAssets.randomElement() {
|
if let randomStream = nonHLSAssets.randomElement() {
|
||||||
let instance = randomStream.0
|
let instance = randomStream.0
|
||||||
@ -68,15 +79,16 @@ extension PlayerModel {
|
|||||||
let url = randomStream.2
|
let url = randomStream.2
|
||||||
let requestRange = randomStream.3
|
let requestRange = randomStream.3
|
||||||
|
|
||||||
|
// swiftlint:disable:next shorthand_optional_binding
|
||||||
if let asset = asset, let instance = instance, !instance.proxiesVideos {
|
if let asset = asset, let instance = instance, !instance.proxiesVideos {
|
||||||
if instance.app == .invidious {
|
if instance.app == .invidious {
|
||||||
testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
|
self.testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
|
||||||
hasForbiddenAsset = isForbidden
|
hasForbiddenAsset = isForbidden
|
||||||
}
|
}
|
||||||
} else if instance.app == .piped {
|
} else if instance.app == .piped {
|
||||||
testPipedAssets(asset: asset, requestRange: requestRange!, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: { isForbidden in
|
self.testPipedAssets(asset: asset, requestRange: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
|
||||||
hasForbiddenAsset = isForbidden
|
hasForbiddenAsset = isForbidden
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let randomHLS = hlsURLs.randomElement() {
|
} else if let randomHLS = hlsURLs.randomElement() {
|
||||||
@ -84,14 +96,15 @@ extension PlayerModel {
|
|||||||
let asset = AVURLAsset(url: randomHLS.1)
|
let asset = AVURLAsset(url: randomHLS.1)
|
||||||
|
|
||||||
if instance?.app == .piped {
|
if instance?.app == .piped {
|
||||||
testPipedAssets(asset: asset, requestRange: nil, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: { isForbidden in
|
self.testPipedAssets(asset: asset, requestRange: nil, isHLS: true, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
|
||||||
hasForbiddenAsset = isForbidden
|
hasForbiddenAsset = isForbidden
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
forbiddenAssetTestGroup.notify(queue: .main) {
|
forbiddenAssetTestGroup.wait()
|
||||||
let processedStreams = streams.map { stream -> Stream in
|
|
||||||
|
// Post-processing code
|
||||||
if let instance = stream.instance {
|
if let instance = stream.instance {
|
||||||
if instance.app == .invidious {
|
if instance.app == .invidious {
|
||||||
if hasForbiddenAsset || instance.proxiesVideos {
|
if hasForbiddenAsset || instance.proxiesVideos {
|
||||||
@ -104,35 +117,36 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
} else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset {
|
} else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset {
|
||||||
if let hlsURL = stream.hlsURL {
|
if let hlsURL = stream.hlsURL {
|
||||||
forbiddenAssetTestGroup.enter()
|
PipedAPI.nonProxiedAsset(url: hlsURL) { possibleNonProxiedURL in
|
||||||
PipedAPI.nonProxiedAsset(url: hlsURL) { nonProxiedURL in
|
if let nonProxiedURL = possibleNonProxiedURL {
|
||||||
if let nonProxiedURL = nonProxiedURL {
|
|
||||||
stream.hlsURL = nonProxiedURL.url
|
stream.hlsURL = nonProxiedURL.url
|
||||||
}
|
}
|
||||||
forbiddenAssetTestGroup.leave()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let audio = stream.audioAsset {
|
if let audio = stream.audioAsset {
|
||||||
forbiddenAssetTestGroup.enter()
|
|
||||||
PipedAPI.nonProxiedAsset(asset: audio) { nonProxiedAudioAsset in
|
PipedAPI.nonProxiedAsset(asset: audio) { nonProxiedAudioAsset in
|
||||||
stream.audioAsset = nonProxiedAudioAsset
|
stream.audioAsset = nonProxiedAudioAsset
|
||||||
forbiddenAssetTestGroup.leave()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let video = stream.videoAsset {
|
if let video = stream.videoAsset {
|
||||||
forbiddenAssetTestGroup.enter()
|
|
||||||
PipedAPI.nonProxiedAsset(asset: video) { nonProxiedVideoAsset in
|
PipedAPI.nonProxiedAsset(asset: video) { nonProxiedVideoAsset in
|
||||||
stream.videoAsset = nonProxiedVideoAsset
|
stream.videoAsset = nonProxiedVideoAsset
|
||||||
forbiddenAssetTestGroup.leave()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return stream
|
|
||||||
}
|
|
||||||
|
|
||||||
forbiddenAssetTestGroup.notify(queue: .main) {
|
// Append to processedStreams within the processedStreamsQueue
|
||||||
|
processedStreamsQueue.sync {
|
||||||
|
processedStreams.append(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
streamProcessingGroup.notify(queue: .main) {
|
||||||
|
// Access and pass processedStreams within the processedStreamsQueue block
|
||||||
|
processedStreamsQueue.sync {
|
||||||
completion(processedStreams)
|
completion(processedStreams)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,21 +175,23 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func testAsset(url: URL, range: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
|
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 randomEnd = Int.random(in: 200 ... 800)
|
||||||
let requestRange = range ?? "0-\(randomEnd)"
|
let requestRange = range ?? "0-\(randomEnd)"
|
||||||
let HTTPStatusForbidden = 403
|
|
||||||
|
|
||||||
forbiddenAssetTestGroup.enter()
|
forbiddenAssetTestGroup.enter()
|
||||||
URLTester.testURLResponse(url: url, range: requestRange, isHLS: isHLS) { statusCode in
|
URLTester.testURLResponse(url: url, range: requestRange, isHLS: isHLS) { statusCode in
|
||||||
completion(statusCode == HTTPStatusForbidden)
|
completion(statusCode == HTTPStatus.Forbidden)
|
||||||
forbiddenAssetTestGroup.leave()
|
forbiddenAssetTestGroup.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
|
private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
|
||||||
PipedAPI.nonProxiedAsset(asset: asset) { nonProxiedAsset in
|
PipedAPI.nonProxiedAsset(asset: asset) { possibleNonProxiedAsset in
|
||||||
if let nonProxiedAsset = nonProxiedAsset {
|
if let nonProxiedAsset = possibleNonProxiedAsset {
|
||||||
self.testAsset(url: nonProxiedAsset.url, range: requestRange, isHLS: isHLS, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: completion)
|
self.testAsset(url: nonProxiedAsset.url, range: requestRange, isHLS: isHLS, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: completion)
|
||||||
|
} else {
|
||||||
|
completion(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
81
Shared/HTTPStatus.swift
Normal file
81
Shared/HTTPStatus.swift
Normal file
@ -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
|
||||||
|
}
|
@ -5,7 +5,6 @@ enum URLTester {
|
|||||||
private static let hlsMediaPrefix = "#EXT-X-MEDIA:"
|
private static let hlsMediaPrefix = "#EXT-X-MEDIA:"
|
||||||
private static let hlsInfPrefix = "#EXTINF:"
|
private static let hlsInfPrefix = "#EXTINF:"
|
||||||
private static let uriRegex = "(?<=URI=\")(.*?)(?=\")"
|
private static let uriRegex = "(?<=URI=\")(.*?)(?=\")"
|
||||||
private static let HTTPStatusForbidden = 403
|
|
||||||
|
|
||||||
static func testURLResponse(url: URL, range: String, isHLS: Bool, completion: @escaping (Int) -> Void) {
|
static func testURLResponse(url: URL, range: String, isHLS: Bool, completion: @escaping (Int) -> Void) {
|
||||||
if isHLS {
|
if isHLS {
|
||||||
@ -24,7 +23,7 @@ enum URLTester {
|
|||||||
|
|
||||||
var dataTask: URLSessionDataTask?
|
var dataTask: URLSessionDataTask?
|
||||||
dataTask = URLSession.shared.dataTask(with: request) { _, response, _ in
|
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)")
|
Logger(label: "stream.yattee.httpRequest").info("URL: \(url) | Status Code: \(statusCode)")
|
||||||
completion(statusCode, dataTask)
|
completion(statusCode, dataTask)
|
||||||
}
|
}
|
||||||
@ -37,6 +36,8 @@ enum URLTester {
|
|||||||
httpRequest(url: url, range: range) { statusCode, _ in
|
httpRequest(url: url, range: range) { statusCode, _ in
|
||||||
completion(statusCode)
|
completion(statusCode)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
completion(HTTPStatus.NotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,10 +65,15 @@ enum URLTester {
|
|||||||
|
|
||||||
private static func parseHLSManifest(manifestUrl: URL, completion: @escaping ([URL]) -> Void) {
|
private static func parseHLSManifest(manifestUrl: URL, completion: @escaping ([URL]) -> Void) {
|
||||||
URLSession.shared.dataTask(with: manifestUrl) { data, _, _ in
|
URLSession.shared.dataTask(with: manifestUrl) { data, _, _ in
|
||||||
guard let data = data,
|
// swiftlint:disable:next shorthand_optional_binding
|
||||||
let manifest = String(data: data, encoding: .utf8),
|
guard let data = data else {
|
||||||
!manifest.isEmpty
|
Logger(label: "stream.yattee.httpRequest").error("Data is nil")
|
||||||
else {
|
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")
|
Logger(label: "stream.yattee.httpRequest").error("Cannot read or empty HLS manifest")
|
||||||
completion([])
|
completion([])
|
||||||
return
|
return
|
||||||
|
@ -1070,6 +1070,9 @@
|
|||||||
37FFC440272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
|
37FFC440272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
|
||||||
37FFC441272734C3009FFD26 /* 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 */; };
|
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 */; };
|
E258F38A2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
|
||||||
E258F38B2BF61BD2005B8C28 /* 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 */; };
|
E258F38C2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
|
||||||
@ -1542,6 +1545,7 @@
|
|||||||
3DA101AD287C30F50027D920 /* DEVELOPMENT_TEAM.template.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.template.xcconfig; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
E258F3892BF61BD2005B8C28 /* URLTester.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLTester.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@ -2290,6 +2294,7 @@
|
|||||||
372915E52687E3B900F5A35B /* Defaults.swift */,
|
372915E52687E3B900F5A35B /* Defaults.swift */,
|
||||||
37D2E0D328B67EFC00F64D52 /* Delay.swift */,
|
37D2E0D328B67EFC00F64D52 /* Delay.swift */,
|
||||||
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */,
|
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */,
|
||||||
|
E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */,
|
||||||
375B537828DF6CBB004C1D19 /* Localizable.strings */,
|
375B537828DF6CBB004C1D19 /* Localizable.strings */,
|
||||||
3729037D2739E47400EA99F6 /* MenuCommands.swift */,
|
3729037D2739E47400EA99F6 /* MenuCommands.swift */,
|
||||||
37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */,
|
37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */,
|
||||||
@ -3120,6 +3125,7 @@
|
|||||||
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
||||||
37F7D82C289EB05F00E2B3D0 /* SettingsPickerModifier.swift in Sources */,
|
37F7D82C289EB05F00E2B3D0 /* SettingsPickerModifier.swift in Sources */,
|
||||||
375EC95D289EEEE000751258 /* QualityProfile.swift in Sources */,
|
375EC95D289EEEE000751258 /* QualityProfile.swift in Sources */,
|
||||||
|
E25028B02BF790F5002CB9FC /* HTTPStatus.swift in Sources */,
|
||||||
3773B8102ADC076800B5FEF3 /* ScrollViewMatcher.swift in Sources */,
|
3773B8102ADC076800B5FEF3 /* ScrollViewMatcher.swift in Sources */,
|
||||||
371B7E662759786B00D21217 /* Comment+Fixtures.swift in Sources */,
|
371B7E662759786B00D21217 /* Comment+Fixtures.swift in Sources */,
|
||||||
37BE0BD326A1D4780092E2DB /* AppleAVPlayerView.swift in Sources */,
|
37BE0BD326A1D4780092E2DB /* AppleAVPlayerView.swift in Sources */,
|
||||||
@ -3593,6 +3599,7 @@
|
|||||||
37D4B19826717E1500C925CA /* Video.swift in Sources */,
|
37D4B19826717E1500C925CA /* Video.swift in Sources */,
|
||||||
371B7E5D27596B8400D21217 /* Comment.swift in Sources */,
|
371B7E5D27596B8400D21217 /* Comment.swift in Sources */,
|
||||||
37EF5C232739D37B00B03725 /* MenuModel.swift in Sources */,
|
37EF5C232739D37B00B03725 /* MenuModel.swift in Sources */,
|
||||||
|
E25028B12BF790F5002CB9FC /* HTTPStatus.swift in Sources */,
|
||||||
37A7D71C2B680E66009CB1ED /* LocationsSettingsGroupExporter.swift in Sources */,
|
37A7D71C2B680E66009CB1ED /* LocationsSettingsGroupExporter.swift in Sources */,
|
||||||
37BC50A92778A84700510953 /* HistorySettings.swift in Sources */,
|
37BC50A92778A84700510953 /* HistorySettings.swift in Sources */,
|
||||||
374DE88128BB896C0062BBF2 /* PlayerDragGesture.swift in Sources */,
|
374DE88128BB896C0062BBF2 /* PlayerDragGesture.swift in Sources */,
|
||||||
@ -3873,6 +3880,7 @@
|
|||||||
37130A61277657300033018A /* PersistenceController.swift in Sources */,
|
37130A61277657300033018A /* PersistenceController.swift in Sources */,
|
||||||
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
|
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
|
||||||
3717407F2949D40800FDDBC7 /* ChannelLinkView.swift in Sources */,
|
3717407F2949D40800FDDBC7 /* ChannelLinkView.swift in Sources */,
|
||||||
|
E25028B22BF790F5002CB9FC /* HTTPStatus.swift in Sources */,
|
||||||
370F4FAA27CC163B001B35DC /* PlayerBackend.swift in Sources */,
|
370F4FAA27CC163B001B35DC /* PlayerBackend.swift in Sources */,
|
||||||
376A33E62720CB35000C1D6B /* Account.swift in Sources */,
|
376A33E62720CB35000C1D6B /* Account.swift in Sources */,
|
||||||
3763C98B290C7A50004D3B5F /* OpenVideosView.swift in Sources */,
|
3763C98B290C7A50004D3B5F /* OpenVideosView.swift in Sources */,
|
||||||
|
Loading…
Reference in New Issue
Block a user