diff --git a/.swiftlint.yml b/.swiftlint.yml index aa8552ddb..4a86d8da0 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -13,6 +13,7 @@ disabled_rules: - syntactic_sugar - for_where - legacy_constant + - non_optional_string_data_conversion opt_in_rules: - attributes diff --git a/DarockBili.xcodeproj/project.pbxproj b/DarockBili.xcodeproj/project.pbxproj index 0527b947c..c3fdece15 100644 --- a/DarockBili.xcodeproj/project.pbxproj +++ b/DarockBili.xcodeproj/project.pbxproj @@ -1214,7 +1214,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_TEAM = B57D8PP775; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SafariExtension/Info.plist; @@ -1245,7 +1245,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_TEAM = B57D8PP775; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SafariExtension/Info.plist; @@ -1342,7 +1342,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBiliAlternative.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B38QUJMY47; ENABLE_PREVIEWS = YES; @@ -1387,7 +1387,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview\\ Content\""; DEVELOPMENT_TEAM = B38QUJMY47; ENABLE_PREVIEWS = YES; @@ -1423,7 +1423,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_TEAM = B57D8PP775; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -1447,7 +1447,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_TEAM = B38QUJMY47; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SafariExtension/Info.plist; @@ -1515,7 +1515,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBili.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1562,7 +1562,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBili.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1607,7 +1607,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview\\ Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1643,7 +1643,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview\\ Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1679,7 +1679,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_TEAM = B57D8PP775; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -1702,7 +1702,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1262; + CURRENT_PROJECT_VERSION = 1279; DEVELOPMENT_TEAM = B57D8PP775; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; diff --git a/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ad5c83ab7..34a131b8e 100644 --- a/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DarockBili.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/adamzarn/AZVideoPlayer", "state" : { - "revision" : "f37c779bf5c00e85dc31ad25d883b3f2e5242b2a", - "version" : "1.2.0" + "revision" : "75e9e05b87d2bd60d83be4b8db280eb2859a86bc", + "version" : "1.3.0" } }, { @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Serene-Garden/Cepheus", "state" : { - "revision" : "38adeadadc1b52a61db4078a80aac98af3311890", - "version" : "2.0.2" + "revision" : "0754853c69a39f6cdc04abb8b1d194a3ed3a483e", + "version" : "2.3.0" } }, { @@ -52,7 +52,7 @@ "location" : "https://github.com/Darock-Studio/DarockKit", "state" : { "branch" : "main", - "revision" : "0735d98596b3fe5280a3d287264150d4ae202076" + "revision" : "4afcdeba8f3c6ca4dbd7ba2886924bdcbadec262" } }, { @@ -97,7 +97,7 @@ "location" : "https://github.com/mixpanel/mixpanel-swift", "state" : { "branch" : "master", - "revision" : "1ae92448ec3b0841ae5ceaa151c98ae66776596a" + "revision" : "e7869c433ee01cdc52aa14abf636fe0b412d72f8" } }, { @@ -105,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/RickeyBoy/ScreenshotableView", "state" : { - "revision" : "3c140215fb12c7e8aef78e853006110350455c5b", - "version" : "1.0.0" + "revision" : "4dcc274e518756c9bb6eabc153b2edb51a7bd482", + "version" : "1.1.0" } }, { @@ -114,8 +114,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SDWebImage/SDWebImage.git", "state" : { - "revision" : "f6afa0132961d593f07970d84e2d8b588c29ea04", - "version" : "5.19.1" + "revision" : "be0bcd7823ce56629948491f2eaeaa19979514f7", + "version" : "5.19.4" } }, { diff --git a/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme b/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme index 2be1ee1d0..2188ba19d 100644 --- a/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme +++ b/DarockBili.xcodeproj/xcshareddata/xcschemes/MeowBili Watch App.xcscheme @@ -96,8 +96,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" - allowLocationSimulation = "YES" - notificationPayloadFile = "PushNotificationPayload.apns"> + allowLocationSimulation = "YES"> <BuildableProductRunnable runnableDebuggingMode = "0"> <BuildableReference diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 7b154a6c1..a54a77066 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -8863,7 +8863,6 @@ }, "手记建议" : { - "extractionState" : "stale", "localizations" : { "zh-Hant" : { "stringUnit" : { @@ -8915,9 +8914,6 @@ }, "播放倍速" : { - }, - "播放卡住/无画面?" : { - }, "文件保险箱" : { @@ -9498,9 +9494,6 @@ }, "通知" : { - }, - "重启 Apple Watch" : { - }, "重新载入" : { "localizations" : { diff --git a/MeowBili/InMain/SearchView.swift b/MeowBili/InMain/SearchView.swift index 292eab4ba..89a882b2a 100644 --- a/MeowBili/InMain/SearchView.swift +++ b/MeowBili/InMain/SearchView.swift @@ -34,6 +34,7 @@ struct SearchMainView: View { @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" @AppStorage("SESSDATA") var sessdata = "" @AppStorage("bili_jct") var biliJct = "" + @AppStorage("IsShowHotsInSearch") var isShowHotsInSearch = true @State var searchText = "" @State var isSearchPresented = false @State var searchHistory = [String]() @@ -76,7 +77,7 @@ struct SearchMainView: View { #endif } } - if !hotSearches.isEmpty { + if !hotSearches.isEmpty && isShowHotsInSearch { Section { if !isHotSearchFolded { ForEach(0..<hotSearches.count, id: \.self) { i in diff --git a/MeowBili/Others/SettingsView.swift b/MeowBili/Others/SettingsView.swift index c7d4f4150..1c54c7c28 100644 --- a/MeowBili/Others/SettingsView.swift +++ b/MeowBili/Others/SettingsView.swift @@ -482,10 +482,12 @@ struct SuggestionViewSettingsView: View { struct NetworkSettingsView: View { @AppStorage("IsShowVideoSuggestionsFromDarock") var isShowVideoSuggestionsFromDarock = true + @AppStorage("IsShowHotsInSearch") var isShowHotsInSearch = true @AppStorage("IsShowNetworkFixing") var isShowNetworkFixing = true var body: some View { List { Section { + Toggle("在搜索页显示热搜", isOn: $isShowHotsInSearch) Toggle("显示来自 Darock 的推荐", isOn: $isShowVideoSuggestionsFromDarock) } Section { diff --git a/MeowBili/PersonalCenter/UserDetailView.swift b/MeowBili/PersonalCenter/UserDetailView.swift index 3c8bde375..611afe7ff 100644 --- a/MeowBili/PersonalCenter/UserDetailView.swift +++ b/MeowBili/PersonalCenter/UserDetailView.swift @@ -1077,8 +1077,8 @@ struct UserDetailView: View { RefreshVideos() } } - .onChange(of: viewSelector) { value in - switch value { + .onChange(of: viewSelector) { _ in + switch viewSelector { case .video: break case .article: diff --git a/MeowBili/Video/VideoDetailView.swift b/MeowBili/Video/VideoDetailView.swift index ba1b91177..0fc96987e 100644 --- a/MeowBili/Video/VideoDetailView.swift +++ b/MeowBili/Video/VideoDetailView.swift @@ -611,7 +611,7 @@ struct VideoDetailView: View { } .sheet(isPresented: $isVideoPlayerPresented, content: { VideoPlayerView(videoDetails: $videoDetails, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID) - .navigationBarHidden(true) + .toolbar(.hidden) }) .containerBackground(for: .navigation) { if !isInLowBatteryMode { @@ -1103,7 +1103,7 @@ struct VideoDetailView: View { }) .sheet(isPresented: $isVideoPlayerPresented, content: { VideoPlayerView(videoDetails: $videoDetails, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID) - .navigationBarHidden(true) + .toolbar(.hidden) }) Button(action: { isMoreMenuPresented = true @@ -1511,7 +1511,7 @@ struct VideoDetailView: View { .sheet(isPresented: $isDownloadPresented, content: { VideoDownloadView(bvid: videoDetails["BV"]!, videoDetails: videoDetails, isPaged: true) }) .sheet(isPresented: $isVideoPlayerPresented, content: { VideoPlayerView(videoDetails: $videoDetails, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID) - .navigationBarHidden(true) + .toolbar(.hidden) }) } } diff --git a/MeowBili/Video/VideoPlayerView.swift b/MeowBili/Video/VideoPlayerView.swift index 7bce17eb9..f3c512e1b 100644 --- a/MeowBili/Video/VideoPlayerView.swift +++ b/MeowBili/Video/VideoPlayerView.swift @@ -19,6 +19,7 @@ import AVKit import SwiftUI import Dynamic +import Combine import DarockKit import Alamofire import AVFoundation @@ -62,9 +63,8 @@ struct VideoPlayerView: View { #endif @State var currentTime: Double = 0.0 @State var playerTimer: Timer? - @State var danmakuTimer: Timer? @State var playProgressTimer: Timer? - @State var player: AVPlayer! + @State var player: AVPlayer! = AVPlayer() @State var isFinishedInit = false @State var willBeginFullScreenPresentation = false @State var showDanmakus = [[String: String]]() @@ -163,7 +163,7 @@ struct VideoPlayerView: View { } .allowsHitTesting(false) .offset(x: -danmakuOffset) - .animation(.smooth, value: danmakuOffset) +// .animation(.linear, value: danmakuOffset) } } .rotationEffect(.degrees(isFullScreen ? 90 : 0)) @@ -184,14 +184,14 @@ struct VideoPlayerView: View { } Section { Picker("播放倍速", selection: $videoSpeed) { - Text("1x").tag(1.0) - Text("1.5x").tag(1.5) - Text("2x").tag(2.0) - Text("3x").tag(3.0) - Text("5x").tag(5.0) + Text("1x").tag(Float(1.0)) + Text("1.5x").tag(Float(1.5)) + Text("2x").tag(Float(2.0)) + Text("3x").tag(Float(3.0)) + Text("5x").tag(Float(5.0)) } - .onChange(of: videoSpeed) { value in - player.rate = value + .onChange(of: videoSpeed) { + player.rate = videoSpeed } Button(action: { player.seek(to: CMTime(seconds: currentTime + 10, preferredTimescale: 1)) @@ -206,17 +206,6 @@ struct VideoPlayerView: View { } header: { Text("播放") } - Section { - Button(role: .destructive, action: { - let window = (Dynamic.UIApplication.sharedApplication.windows.asArray! as! [Any]).first! - let dwindow = Dynamic(window) - while true { dwindow.snapshotView(afterScreenUpdates: false) } - }, label: { - Text("重启 Apple Watch") - }) - } header: { - Text("播放卡住/无画面?") - } } .tag(2) } @@ -264,10 +253,10 @@ struct VideoPlayerView: View { player.seek(to: CMTime(seconds: UserDefaults.standard.double(forKey: "\(videoBvid)\(videoCID)PlayTime"), preferredTimescale: 1)) - if let coverData = try? Data(contentsOf: URL(string: videoDetails["Pic"] ?? "") ?? URL(string: "http://example.com")!) { - let cover = UIImage(data: coverData) ?? UIImage() - NowPlayingExtension.setPlayingInfoTitle(videoDetails["Title"]!, artist: videoDetails["UP"]!, artwork: cover) - } +// if let coverData = try? Data(contentsOf: URL(string: videoDetails["Pic"] ?? "") ?? URL(string: "http://example.com")!) { +// let cover = UIImage(data: coverData) ?? UIImage() +// NowPlayingExtension.setPlayingInfoTitle(videoDetails["Title"]!, artist: videoDetails["UP"]!, artwork: cover) +// } let headers: HTTPHeaders = [ "cookie": "SESSDATA=\(sessdata)" @@ -299,13 +288,6 @@ struct VideoPlayerView: View { UpdateDanmaku() - danmakuTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in - danmakuOffset = player.currentTime().seconds * 50 - #if !os(watchOS) - currentPlayTime = player.currentTime().seconds - #endif - } - playProgressTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in if (player.currentItem?.duration.seconds ?? 0) - player.currentTime().seconds > 10 { UserDefaults.standard.set(player.currentTime().seconds, forKey: "\(videoBvid)\(videoCID)PlayTime") @@ -329,7 +311,6 @@ struct VideoPlayerView: View { } #endif playerTimer?.invalidate() - danmakuTimer?.invalidate() playProgressTimer?.invalidate() player?.pause() if (player.currentItem?.duration.seconds ?? 0) - player.currentTime().seconds > 10 { @@ -338,8 +319,8 @@ struct VideoPlayerView: View { UserDefaults.standard.removeObject(forKey: "\(videoBvid)\(videoCID)PlayTime") } } - .onChange(of: videoLink) { value in - let asset = AVURLAsset(url: URL(string: value)!, options: ["AVURLAssetHTTPHeaderFieldsKey": ["User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "Referer": "https://www.bilibili.com"]]) + .onChange(of: videoLink) { + let asset = AVURLAsset(url: URL(string: videoLink)!, options: ["AVURLAssetHTTPHeaderFieldsKey": ["User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "Referer": "https://www.bilibili.com"]]) let item = AVPlayerItem(asset: asset) player?.pause() player = nil @@ -351,9 +332,16 @@ struct VideoPlayerView: View { danmakuOffset = 0 UpdateDanmaku() } + .onReceive(player.periodicTimePublisher()) { time in + danmakuOffset = time.seconds * 50 + #if !os(watchOS) + currentPlayTime = time.seconds + #endif + currentTime = time.seconds + } #if !os(watchOS) - .onChange(of: shouldPause) { value in - if value == true { + .onChange(of: shouldPause) { + if shouldPause { player?.pause() shouldPause = false } @@ -431,10 +419,7 @@ struct VideoPlayerView: View { #else // Less danmakus for watch if showDanmakus.count > 200 { - for _ in 1...5000 { - if showDanmakus.count <= 200 { - break - } + while showDanmakus.count > 200 { showDanmakus.remove(at: Int.random(in: 0..<showDanmakus.count)) } } @@ -472,3 +457,57 @@ struct VideoPlayerView: View { } #endif } + +// Extensions for periodicTimePublisher +extension AVPlayer { + func periodicTimePublisher(forInterval interval: CMTime = CMTime(seconds: 0.5, + preferredTimescale: CMTimeScale(NSEC_PER_SEC))) -> AnyPublisher<CMTime, Never> { + Publisher(self, forInterval: interval) + .eraseToAnyPublisher() + } +} +fileprivate extension AVPlayer { + private struct Publisher: Combine.Publisher { + typealias Output = CMTime + typealias Failure = Never + + var player: AVPlayer + var interval: CMTime + + init(_ player: AVPlayer, forInterval interval: CMTime) { + self.player = player + self.interval = interval + } + + func receive<S>(subscriber: S) where S: Subscriber, Publisher.Failure == S.Failure, Publisher.Output == S.Input { + let subscription = CMTime.Subscription(subscriber: subscriber, player: player, forInterval: interval) + subscriber.receive(subscription: subscription) + } + } +} +fileprivate extension CMTime { + final class Subscription<SubscriberType: Subscriber>: Combine.Subscription where SubscriberType.Input == CMTime, SubscriberType.Failure == Never { + var player: AVPlayer? + var observer: Any? + + init(subscriber: SubscriberType, player: AVPlayer, forInterval interval: CMTime) { + self.player = player + observer = player.addPeriodicTimeObserver(forInterval: interval, queue: nil) { time in + _ = subscriber.receive(time) + } + } + + func request(_ demand: Subscribers.Demand) { + // We do nothing here as we only want to send events when they occur. + // See, for more info: https://developer.apple.com/documentation/combine/subscribers/demand + } + + func cancel() { + if let observer = observer { + player?.removeTimeObserver(observer) + } + observer = nil + player = nil + } + } +}