mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Implement SponsorBlock API
This commit is contained in:
parent
9d7abda63f
commit
d551dee426
@ -117,6 +117,14 @@ struct PlayerViewController: UIViewControllerRepresentable {
|
||||
#if os(tvOS)
|
||||
controller.transportBarCustomMenuItems = items
|
||||
#endif
|
||||
|
||||
if let skip = skipSegmentAction {
|
||||
if controller.contextualActions.isEmpty {
|
||||
controller.contextualActions = [skip]
|
||||
}
|
||||
} else {
|
||||
controller.contextualActions = []
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var streamingQualityMenu: UIMenu {
|
||||
@ -149,4 +157,16 @@ struct PlayerViewController: UIViewControllerRepresentable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var skipSegmentAction: UIAction? {
|
||||
if state.currentSegment == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return UIAction(title: "Skip \(state.currentSegment!.title())") { _ in
|
||||
DispatchQueue.main.async {
|
||||
state.player.seek(to: state.currentSegment!.skipTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,23 +14,34 @@ final class PlayerState: ObservableObject {
|
||||
@Published private(set) var streamToLoad: Stream!
|
||||
@Published private(set) var streamLoading = false
|
||||
|
||||
@Published private(set) var currentTime: CMTime?
|
||||
@Published private(set) var savedTime: CMTime?
|
||||
@Published var currentSegment: Segment?
|
||||
|
||||
var playerItem: AVPlayerItem {
|
||||
let playerItem = AVPlayerItem(asset: composition)
|
||||
|
||||
playerItem.externalMetadata = [makeMetadataItem(.commonIdentifierTitle, value: video.title)]
|
||||
playerItem.externalMetadata = [
|
||||
makeMetadataItem(.commonIdentifierTitle, value: video.title),
|
||||
makeMetadataItem(.quickTimeMetadataGenre, value: video.genre),
|
||||
makeMetadataItem(.commonIdentifierDescription, value: video.description)
|
||||
]
|
||||
playerItem.preferredForwardBufferDuration = 10
|
||||
|
||||
return playerItem
|
||||
}
|
||||
|
||||
var segmentsProvider: SponsorBlockSegmentsProvider
|
||||
var timeObserver: Any?
|
||||
|
||||
init(_ video: Video) {
|
||||
self.video = video
|
||||
segmentsProvider = SponsorBlockSegmentsProvider(video.id)
|
||||
|
||||
segmentsProvider.load()
|
||||
}
|
||||
|
||||
deinit {
|
||||
print("destr deinit")
|
||||
destroyPlayer()
|
||||
}
|
||||
|
||||
@ -51,12 +62,15 @@ final class PlayerState: ObservableObject {
|
||||
func streamDidLoad(_ stream: Stream?) {
|
||||
logger.info("didload stream: \(stream!.description)")
|
||||
|
||||
currentStream?.cancelLoadingAssets()
|
||||
currentStream = stream
|
||||
streamLoading = streamToLoad != stream
|
||||
|
||||
if streamToLoad == stream {
|
||||
streamToLoad = nil
|
||||
}
|
||||
|
||||
addTimeObserver()
|
||||
}
|
||||
|
||||
func cancelLoadingStream(_ stream: Stream) {
|
||||
@ -121,6 +135,18 @@ final class PlayerState: ObservableObject {
|
||||
|
||||
player.cancelPendingPrerolls()
|
||||
player.replaceCurrentItem(with: nil)
|
||||
|
||||
if timeObserver != nil {
|
||||
player.removeTimeObserver(timeObserver!)
|
||||
}
|
||||
}
|
||||
|
||||
func addTimeObserver() {
|
||||
let interval = CMTime(value: 1, timescale: 1)
|
||||
timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
|
||||
self.currentTime = time
|
||||
self.currentSegment = self.segmentsProvider.segments.first { $0.timeInSegment(time) }
|
||||
}
|
||||
}
|
||||
|
||||
private func makeMetadataItem(_ identifier: AVMetadataIdentifier, value: Any) -> AVMetadataItem {
|
||||
|
38
Model/Segment.swift
Normal file
38
Model/Segment.swift
Normal file
@ -0,0 +1,38 @@
|
||||
import CoreMedia
|
||||
import Foundation
|
||||
import SwiftyJSON
|
||||
|
||||
// swiftlint:disable:next final_class
|
||||
class Segment: ObservableObject, Hashable {
|
||||
let category: String
|
||||
let segment: [Double]
|
||||
let uuid: String
|
||||
let videoDuration: Int
|
||||
|
||||
init(category: String, segment: [Double], uuid: String, videoDuration: Int) {
|
||||
self.category = category
|
||||
self.segment = segment
|
||||
self.uuid = uuid
|
||||
self.videoDuration = videoDuration
|
||||
}
|
||||
|
||||
func timeInSegment(_ time: CMTime) -> Bool {
|
||||
(segment.first! ... segment.last!).contains(time.seconds)
|
||||
}
|
||||
|
||||
var skipTo: CMTime {
|
||||
CMTime(seconds: segment.last!, preferredTimescale: 1)
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(uuid)
|
||||
}
|
||||
|
||||
static func == (lhs: Segment, rhs: Segment) -> Bool {
|
||||
lhs.uuid == rhs.uuid
|
||||
}
|
||||
|
||||
func title() -> String {
|
||||
category
|
||||
}
|
||||
}
|
24
Model/SponsorBlockSegment.swift
Normal file
24
Model/SponsorBlockSegment.swift
Normal file
@ -0,0 +1,24 @@
|
||||
import Foundation
|
||||
import SwiftyJSON
|
||||
|
||||
final class SponsorBlockSegment: Segment {
|
||||
init(_ json: JSON) {
|
||||
super.init(
|
||||
category: json["category"].string!,
|
||||
segment: json["segment"].array!.map { $0.double! },
|
||||
uuid: json["UUID"].string!,
|
||||
videoDuration: json["videoDuration"].int!
|
||||
)
|
||||
}
|
||||
|
||||
override func title() -> String {
|
||||
switch category {
|
||||
case "selfpromo":
|
||||
return "self-promotion"
|
||||
case "music_offtopic":
|
||||
return "to music"
|
||||
default:
|
||||
return category
|
||||
}
|
||||
}
|
||||
}
|
35
Model/SponsorBlockSegmentsProvider.swift
Normal file
35
Model/SponsorBlockSegmentsProvider.swift
Normal file
@ -0,0 +1,35 @@
|
||||
import Alamofire
|
||||
import Foundation
|
||||
import SwiftyJSON
|
||||
|
||||
final class SponsorBlockSegmentsProvider: ObservableObject {
|
||||
let categories = ["sponsor", "selfpromo", "outro", "intro", "music_offtopic", "interaction"]
|
||||
|
||||
@Published var video: Video?
|
||||
|
||||
@Published var segments = [Segment]()
|
||||
|
||||
var id: String
|
||||
|
||||
init(_ id: String) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
func load() {
|
||||
AF.request("https://sponsor.ajay.app/api/skipSegments", parameters: parameters).responseJSON { response in
|
||||
switch response.result {
|
||||
case let .success(value):
|
||||
self.segments = JSON(value).arrayValue.map { SponsorBlockSegment($0) }
|
||||
case let .failure(error):
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var parameters: [String: String] {
|
||||
[
|
||||
"videoID": id,
|
||||
"categories": JSON(categories).rawString(String.Encoding.utf8)!
|
||||
]
|
||||
}
|
||||
}
|
@ -13,6 +13,6 @@ final class TrendingCountriesProvider: DataProvider {
|
||||
}
|
||||
|
||||
self.query = query
|
||||
countries = Country.searchByName(query)
|
||||
countries = Country.search(query)
|
||||
}
|
||||
}
|
||||
|
@ -12,29 +12,11 @@ final class Video: Identifiable, ObservableObject {
|
||||
var published: String
|
||||
var views: Int
|
||||
var channelID: String
|
||||
var description: String
|
||||
var genre: String
|
||||
|
||||
var streams = [Stream]()
|
||||
|
||||
init(
|
||||
id: String,
|
||||
title: String,
|
||||
thumbnailURL: URL?,
|
||||
author: String,
|
||||
length: TimeInterval,
|
||||
published: String,
|
||||
views: Int,
|
||||
channelID: String
|
||||
) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.thumbnailURL = thumbnailURL
|
||||
self.author = author
|
||||
self.length = length
|
||||
self.published = published
|
||||
self.views = views
|
||||
self.channelID = channelID
|
||||
}
|
||||
|
||||
init(_ json: JSON) {
|
||||
id = json["videoId"].stringValue
|
||||
title = json["title"].stringValue
|
||||
@ -43,6 +25,9 @@ final class Video: Identifiable, ObservableObject {
|
||||
published = json["publishedText"].stringValue
|
||||
views = json["viewCount"].intValue
|
||||
channelID = json["authorId"].stringValue
|
||||
description = json["description"].stringValue
|
||||
genre = json["genre"].stringValue
|
||||
|
||||
thumbnailURL = extractThumbnailURL(from: json)
|
||||
|
||||
streams = extractFormatStreams(from: json["formatStreams"].arrayValue)
|
||||
|
@ -82,6 +82,9 @@
|
||||
37B767DC2677C3CA0098BAA8 /* PlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B767DA2677C3CA0098BAA8 /* PlayerState.swift */; };
|
||||
37B767DD2677C3CA0098BAA8 /* PlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B767DA2677C3CA0098BAA8 /* PlayerState.swift */; };
|
||||
37B767E02678C5BF0098BAA8 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 37B767DF2678C5BF0098BAA8 /* Logging */; };
|
||||
37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
|
||||
37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
|
||||
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
|
||||
37C7A9042679059200E721B4 /* AVKeyValueStatus+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A9032679059200E721B4 /* AVKeyValueStatus+String.swift */; };
|
||||
37C7A905267905AE00E721B4 /* AVKeyValueStatus+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A9032679059200E721B4 /* AVKeyValueStatus+String.swift */; };
|
||||
37C7A906267905AF00E721B4 /* AVKeyValueStatus+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A9032679059200E721B4 /* AVKeyValueStatus+String.swift */; };
|
||||
@ -128,6 +131,12 @@
|
||||
37D4B1B42672A30700C925CA /* VideoDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */; };
|
||||
37D4B1B52672A30700C925CA /* VideoDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */; };
|
||||
37D4B1B62672A30700C925CA /* VideoDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */; };
|
||||
37EAD86B267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAD86A267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift */; };
|
||||
37EAD86C267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAD86A267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift */; };
|
||||
37EAD86D267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAD86A267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift */; };
|
||||
37EAD86F267B9ED100D9E01B /* Segment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAD86E267B9ED100D9E01B /* Segment.swift */; };
|
||||
37EAD870267B9ED100D9E01B /* Segment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAD86E267B9ED100D9E01B /* Segment.swift */; };
|
||||
37EAD871267B9ED100D9E01B /* Segment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EAD86E267B9ED100D9E01B /* Segment.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -177,6 +186,7 @@
|
||||
37AAF29B26741B5F007FC770 /* SubscriptionVideosProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionVideosProvider.swift; sourceTree = "<group>"; };
|
||||
37AAF29F26741C97007FC770 /* SubscriptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsView.swift; sourceTree = "<group>"; };
|
||||
37B767DA2677C3CA0098BAA8 /* PlayerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerState.swift; sourceTree = "<group>"; };
|
||||
37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorBlockSegment.swift; sourceTree = "<group>"; };
|
||||
37C7A9032679059200E721B4 /* AVKeyValueStatus+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVKeyValueStatus+String.swift"; sourceTree = "<group>"; };
|
||||
37CEE4B42677B628005A1EFE /* StreamType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamType.swift; sourceTree = "<group>"; };
|
||||
37CEE4B82677B63F005A1EFE /* StreamResolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamResolution.swift; sourceTree = "<group>"; };
|
||||
@ -202,6 +212,8 @@
|
||||
37D4B1AE26729DEB00C925CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
37D4B1AF2672A01000C925CA /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = "<group>"; };
|
||||
37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetailsProvider.swift; sourceTree = "<group>"; };
|
||||
37EAD86A267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorBlockSegmentsProvider.swift; sourceTree = "<group>"; };
|
||||
37EAD86E267B9ED100D9E01B /* Segment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Segment.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -376,16 +388,19 @@
|
||||
37B767DA2677C3CA0098BAA8 /* PlayerState.swift */,
|
||||
37D4B19226717CE100C925CA /* PopularVideosProvider.swift */,
|
||||
37AAF2812673791F007FC770 /* SearchedVideosProvider.swift */,
|
||||
37EAD86E267B9ED100D9E01B /* Segment.swift */,
|
||||
37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */,
|
||||
37EAD86A267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift */,
|
||||
37CEE4C02677B697005A1EFE /* Stream.swift */,
|
||||
37CEE4B82677B63F005A1EFE /* StreamResolution.swift */,
|
||||
37CEE4B42677B628005A1EFE /* StreamType.swift */,
|
||||
37AAF29B26741B5F007FC770 /* SubscriptionVideosProvider.swift */,
|
||||
3705B181267B4E4900704544 /* TrendingCategory.swift */,
|
||||
3714167A267AA1CF006CA35D /* TrendingCountriesProvider.swift */,
|
||||
37141676267A9AAD006CA35D /* TrendingState.swift */,
|
||||
3714167E267AB55D006CA35D /* TrendingVideosProvider.swift */,
|
||||
37D4B19626717E1500C925CA /* Video.swift */,
|
||||
37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */,
|
||||
3714167E267AB55D006CA35D /* TrendingVideosProvider.swift */,
|
||||
3705B181267B4E4900704544 /* TrendingCategory.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@ -639,6 +654,7 @@
|
||||
37D4B19326717CE100C925CA /* PopularVideosProvider.swift in Sources */,
|
||||
37AAF29C26741B5F007FC770 /* SubscriptionVideosProvider.swift in Sources */,
|
||||
37141668267A83F9006CA35D /* StreamAVPlayerViewController.swift in Sources */,
|
||||
37EAD86B267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift in Sources */,
|
||||
377FC7E6267A085600A6BBAF /* PlayerView.swift in Sources */,
|
||||
37CEE4C12677B697005A1EFE /* Stream.swift in Sources */,
|
||||
37141677267A9AAD006CA35D /* TrendingState.swift in Sources */,
|
||||
@ -646,6 +662,7 @@
|
||||
377FC7DC267A081800A6BBAF /* PopularVideosView.swift in Sources */,
|
||||
3714167F267AB55D006CA35D /* TrendingVideosProvider.swift in Sources */,
|
||||
3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
||||
37EAD86F267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||
37CEE4B52677B628005A1EFE /* StreamType.swift in Sources */,
|
||||
3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
377FC7E3267A084A00A6BBAF /* VideoThumbnailView.swift in Sources */,
|
||||
@ -657,6 +674,7 @@
|
||||
377FC7E9267A085D00A6BBAF /* PlayerViewController.swift in Sources */,
|
||||
377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */,
|
||||
377FC7E1267A082600A6BBAF /* ChannelView.swift in Sources */,
|
||||
37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||
37C7A9042679059200E721B4 /* AVKeyValueStatus+String.swift in Sources */,
|
||||
37B767DB2677C3CA0098BAA8 /* PlayerState.swift in Sources */,
|
||||
37D4B1B42672A30700C925CA /* VideoDetailsProvider.swift in Sources */,
|
||||
@ -678,6 +696,7 @@
|
||||
37D4B19426717CE100C925CA /* PopularVideosProvider.swift in Sources */,
|
||||
37AAF29D26741B5F007FC770 /* SubscriptionVideosProvider.swift in Sources */,
|
||||
37141669267A83F9006CA35D /* StreamAVPlayerViewController.swift in Sources */,
|
||||
37EAD86C267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift in Sources */,
|
||||
377FC7E7267A085600A6BBAF /* PlayerView.swift in Sources */,
|
||||
37CEE4C22677B697005A1EFE /* Stream.swift in Sources */,
|
||||
37141678267A9AAD006CA35D /* TrendingState.swift in Sources */,
|
||||
@ -685,6 +704,7 @@
|
||||
377FC7DD267A081A00A6BBAF /* PopularVideosView.swift in Sources */,
|
||||
37141680267AB55D006CA35D /* TrendingVideosProvider.swift in Sources */,
|
||||
3705B183267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
||||
37EAD870267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||
37CEE4B62677B628005A1EFE /* StreamType.swift in Sources */,
|
||||
37141670267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
377FC7E2267A084A00A6BBAF /* VideoThumbnailView.swift in Sources */,
|
||||
@ -696,6 +716,7 @@
|
||||
377FC7E8267A085D00A6BBAF /* PlayerViewController.swift in Sources */,
|
||||
377FC7E4267A084E00A6BBAF /* SearchView.swift in Sources */,
|
||||
377FC7E0267A082600A6BBAF /* ChannelView.swift in Sources */,
|
||||
37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||
37C7A906267905AF00E721B4 /* AVKeyValueStatus+String.swift in Sources */,
|
||||
37B767DC2677C3CA0098BAA8 /* PlayerState.swift in Sources */,
|
||||
37D4B1B52672A30700C925CA /* VideoDetailsProvider.swift in Sources */,
|
||||
@ -730,6 +751,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
37AAF28026737550007FC770 /* SearchView.swift in Sources */,
|
||||
37EAD871267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||
37CEE4BF2677B670005A1EFE /* AudioVideoStream.swift in Sources */,
|
||||
37CEE4B72677B628005A1EFE /* StreamType.swift in Sources */,
|
||||
3714166A267A83F9006CA35D /* StreamAVPlayerViewController.swift in Sources */,
|
||||
@ -742,6 +764,7 @@
|
||||
37D4B1B22672A01000C925CA /* DataProvider.swift in Sources */,
|
||||
37141671267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
37AAF29226740715007FC770 /* AppState.swift in Sources */,
|
||||
37EAD86D267B9C5600D9E01B /* SponsorBlockSegmentsProvider.swift in Sources */,
|
||||
3705B17C267B4D9A00704544 /* VisualEffectView.swift in Sources */,
|
||||
3741B5302676213400125C5E /* PlayerViewController.swift in Sources */,
|
||||
37B767DD2677C3CA0098BAA8 /* PlayerState.swift in Sources */,
|
||||
@ -751,6 +774,7 @@
|
||||
37AAF27E26737323007FC770 /* PopularVideosView.swift in Sources */,
|
||||
37AAF29A26740A01007FC770 /* VideosView.swift in Sources */,
|
||||
37AAF2962674086B007FC770 /* TabSelection.swift in Sources */,
|
||||
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||
37CEE4C32677B697005A1EFE /* Stream.swift in Sources */,
|
||||
37AAF28A2673AB89007FC770 /* ChannelView.swift in Sources */,
|
||||
37AAF28E2673ABD3007FC770 /* ChannelVideosProvider.swift in Sources */,
|
||||
|
@ -521,10 +521,18 @@ extension Country {
|
||||
return result
|
||||
}
|
||||
|
||||
static func searchByName(_ name: String) -> [Country] {
|
||||
let countries = filteredCountries { stringFolding($0) == stringFolding(name) }
|
||||
static func search(_ query: String) -> [Country] {
|
||||
if let country = searchByCode(query) {
|
||||
return [country]
|
||||
}
|
||||
|
||||
return countries.isEmpty ? searchByPartialName(name) : countries
|
||||
let countries = filteredCountries { stringFolding($0) == stringFolding(query) }
|
||||
|
||||
return countries.isEmpty ? searchByPartialName(query) : countries
|
||||
}
|
||||
|
||||
static func searchByCode(_ code: String) -> Country? {
|
||||
Country(rawValue: code.uppercased())
|
||||
}
|
||||
|
||||
static func searchByPartialName(_ name: String) -> [Country] {
|
||||
@ -540,7 +548,8 @@ extension Country {
|
||||
}
|
||||
|
||||
private static func filteredCountries(_ predicate: (String) -> Bool) -> [Country] {
|
||||
Country.allCases.map { $0.name }
|
||||
Country.allCases
|
||||
.map { $0.name }
|
||||
.filter(predicate)
|
||||
.compactMap { string in Country.allCases.first { $0.name == string } }
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user