Эх сурвалжийг харах

feat: 补充语音条播放逻辑

陈文艺 3 сар өмнө
parent
commit
b417e666d6

+ 3 - 8
Lanu.xcodeproj/project.pbxproj

@@ -93,6 +93,7 @@
 				Config_Debug.xcconfig,
 				Config_Release.xcconfig,
 				"Files/Font/Poppins-SemiBold.ttf",
+				Files/Svga/ic_login_gender_male_anim.svga,
 				"GoogleService-Info-Debug.plist",
 				"GoogleService-Info-Release.plist",
 				Localizable.xcstrings,
@@ -287,6 +288,8 @@
 		};
 		FBB67E232EC48B440070E686 /* ThirdParty */ = {
 			isa = PBXFileSystemSynchronizedRootGroup;
+			exceptions = (
+			);
 			path = ThirdParty;
 			sourceTree = "<group>";
 		};
@@ -439,14 +442,10 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-resources-${CONFIGURATION}-input-files.xcfilelist",
 			);
-			inputPaths = (
-			);
 			name = "[CP] Copy Pods Resources";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-resources-${CONFIGURATION}-output-files.xcfilelist",
 			);
-			outputPaths = (
-			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-resources.sh\"\n";
@@ -482,14 +481,10 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 			);
-			inputPaths = (
-			);
 			name = "[CP] Embed Pods Frameworks";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 			);
-			outputPaths = (
-			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Lanu/Pods-Lanu-frameworks.sh\"\n";

+ 7 - 0
Lanu/Common/Views/Base/LNNavigationController.swift

@@ -34,10 +34,17 @@ class LNNavigationController: UINavigationController {
     override func pushViewController(_ viewController: UIViewController, animated: Bool) {
         if hasLogin || whileListVC.contains(where: { $0 == type(of: viewController) }) {
             super.pushViewController(viewController, animated: animated)
+            LNVoicePlayer.shared.stop()
             return
         }
         showLoginPanel()
     }
+    
+    override func popViewController(animated: Bool) -> UIViewController? {
+        LNVoicePlayer.shared.stop()
+        
+        return super.popViewController(animated: animated)
+    }
 }
 
 extension LNNavigationController: LNAccountManagerNotify {

+ 44 - 0
Lanu/Common/Voice/LNVoicePlayer.swift

@@ -7,6 +7,7 @@
 
 import Foundation
 import AVFAudio
+import AVFoundation
 
 
 protocol LNVoicePlayerNotify {
@@ -58,6 +59,8 @@ class LNVoicePlayer: NSObject {
         curPlayer?.rate ?? 1.0
     }
     
+    private var loadValueAssets: [String: AVURLAsset] = [:]
+    
     private override init() { super.init() }
     
     func play(path: String) {
@@ -153,6 +156,47 @@ class LNVoicePlayer: NSObject {
                 }
         })
     }
+    
+    func getRemoteAudioDuration(urlStr: String, completion: @escaping (TimeInterval?, Error?) -> Void) {
+        guard let url = URL(string: urlStr) else {
+            completion(0, nil)
+            return
+        }
+        
+        let options: [String: Any] = [
+            AVURLAssetPreferPreciseDurationAndTimingKey: true
+        ]
+        
+        let asset = AVURLAsset(url: url, options: options)
+        loadValueAssets[urlStr] = asset
+        
+        asset.loadValuesAsynchronously(forKeys: ["duration"]) { [weak self] in
+            guard let self else { return }
+            var error: NSError?
+            let status = asset.statusOfValue(forKey: "duration", error: &error)
+            
+            DispatchQueue.main.async { [weak self] in
+                guard let self else { return }
+                switch status {
+                case .loaded:
+                    let duration = asset.duration.seconds
+                    completion(duration, nil)
+                case .failed:
+                    completion(nil, error)
+                case .cancelled:
+                    completion(nil, NSError(domain: "AudioDuration", code: -1, userInfo: [NSLocalizedDescriptionKey: "加载取消"]))
+                default:
+                    completion(nil, NSError(domain: "AudioDuration", code: -2, userInfo: [NSLocalizedDescriptionKey: "加载失败"]))
+                }
+                loadValueAssets.removeValue(forKey: urlStr)
+            }
+        }
+    }
+    
+    func cancelLoadingAsset(urlStr: String) {
+        guard let asset = loadValueAssets.removeValue(forKey: urlStr) else { return }
+        asset.cancelLoading()
+    }
 }
 
 extension LNVoicePlayer {

BIN
Lanu/Files/Svga/ic_login_gender_male_anim.svga


+ 31 - 3
Lanu/Views/Game/MateList/LNGameMateListCell.swift

@@ -12,7 +12,10 @@ import SnapKit
 
 class LNGameMateListCell: UITableViewCell {
     private let avatar = UIImageView()
+    
+    private let playButton = UIButton()
     private let voiceLabel = UILabel()
+    
     private let priceLabel = UILabel()
     private let unitLabel = UILabel()
     
@@ -34,6 +37,8 @@ class LNGameMateListCell: UITableViewCell {
     private let photoSize = 72
     private let photoStackView = UIStackView()
     
+    private var curItem: LNGameMateListItemVO?
+    
     override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
         super.init(style: style, reuseIdentifier: reuseIdentifier)
         
@@ -44,7 +49,6 @@ class LNGameMateListCell: UITableViewCell {
         avatar.sd_setImage(with: URL(string: item.avatar))
         priceLabel.text = item.price.toDisplay
         unitLabel.text = "/\(item.unit)"
-        voiceLabel.isHidden = item.voiceBar.isEmpty
         
         nameLabel.text = item.nickname
         genderView.update(item.gender, item.age)
@@ -59,6 +63,24 @@ class LNGameMateListCell: UITableViewCell {
         bioLabel.text = item.summary
         
         updatePhotos(item.images)
+        
+        curItem = item
+        
+        if let curItem {
+            LNVoicePlayer.shared.cancelLoadingAsset(urlStr: curItem.voiceBar)
+        }
+        if !item.voiceBar.isEmpty {
+            LNVoicePlayer.shared.getRemoteAudioDuration(urlStr: item.voiceBar)
+            { [weak self] duration, error in
+                guard let self else { return }
+                guard let duration, error == nil else { return }
+                guard curItem?.id == item.id else { return }
+                voiceLabel.text = "\(Int(duration.rounded()))\""
+                playButton.isHidden = false
+            }
+        } else {
+            playButton.isHidden = true
+        }
     }
     
     required init?(coder: NSCoder) {
@@ -165,10 +187,15 @@ extension LNGameMateListCell {
             make.bottom.equalToSuperview()
         }
         
-        let playButton = UIButton()
         playButton.setBackgroundImage(.primary_7, for: .normal)
         playButton.layer.cornerRadius = 11
         playButton.clipsToBounds = true
+        playButton.addAction(UIAction(handler: { [weak self] _ in
+            guard let self else { return }
+            guard let curItem, !curItem.voiceBar.isEmpty else { return }
+            
+            LNVoicePlayer.shared.play(curItem.voiceBar)
+        }), for: .touchUpInside)
         container.addSubview(playButton)
         playButton.snp.makeConstraints { make in
             make.centerX.equalToSuperview()
@@ -178,6 +205,7 @@ extension LNGameMateListCell {
         }
         
         let voice = UIView()
+        voice.isUserInteractionEnabled = false
         playButton.addSubview(voice)
         voice.snp.makeConstraints { make in
             make.center.equalToSuperview()
@@ -189,7 +217,7 @@ extension LNGameMateListCell {
         ic.snp.makeConstraints { make in
             make.leading.centerY.equalToSuperview()
         }
-        voiceLabel.text = .init(key: "15“")
+        
         voiceLabel.font = .heading_h5
         voiceLabel.textColor = .text_1
         voice.addSubview(voiceLabel)

+ 16 - 2
Lanu/Views/Game/Skill/LNSkillVoiceBarView.swift

@@ -12,6 +12,7 @@ import SnapKit
 
 class LNSkillVoiceBarView: UIView {
     private let durationLabel = UILabel()
+    private var curUrl: String?
     
     override init(frame: CGRect) {
         super.init(frame: frame)
@@ -20,8 +21,16 @@ class LNSkillVoiceBarView: UIView {
     }
     
     func setVoice(_ url: String) {
-        durationLabel.text = "15“"
-        isHidden = url.isEmpty
+        curUrl = url
+        LNVoicePlayer.shared.getRemoteAudioDuration(urlStr: url)
+        { [weak self] duration, err in
+            guard let self else { return }
+            guard let duration, err == nil else { return }
+            guard curUrl == url else { return }
+            
+            isHidden = duration <= 0
+            durationLabel.text = "\(Int(duration.rounded()))\""
+        }
     }
     
     required init?(coder: NSCoder) {
@@ -37,6 +46,11 @@ extension LNSkillVoiceBarView {
         button.setBackgroundImage(.primary_7, for: .normal)
         button.layer.cornerRadius = 15.5
         button.clipsToBounds = true
+        button.addAction(UIAction(handler: { [weak self] _ in
+            guard let self else { return }
+            guard let curUrl else { return }
+            LNVoicePlayer.shared.play(curUrl)
+        }), for: .touchUpInside)
         addSubview(button)
         button.snp.makeConstraints { make in
             make.directionalEdges.equalToSuperview()

+ 8 - 1
Lanu/Views/Home/LNHomeViewController.swift

@@ -48,12 +48,19 @@ extension LNHomeViewController: UIScrollViewDelegate {
     }
 }
 
-extension LNHomeViewController: LNAccountManagerNotify {
+extension LNHomeViewController: LNAccountManagerNotify, LNNetworkMonitorNotify {
     func onUserLogin() {
         if stackView.arrangedSubviews.isEmpty {
             loadGameTypes()
         }
     }
+    
+    func onNetworkStateChanged(state: LNNetworkState) {
+        if state == .available,
+            stackView.arrangedSubviews.isEmpty {
+            loadGameTypes()
+        }
+    }
 }
 
 extension LNHomeViewController {

+ 4 - 4
Lanu/Views/Main/LNMainViewController.swift

@@ -61,12 +61,12 @@ extension LNMainViewController: UITabBarControllerDelegate {
     }
 }
 
-extension LNMainViewController: LNProfileManagerNotify {
+extension LNMainViewController: LNProfileManagerNotify, LNNetworkMonitorNotify {
     func onUserInfoChanged(userInfo: LNUserProfileVO) {
         guard userInfo.userNo.isMyUid else { return }
         
-//        if !userInfo.isAvailable {
-//            view.pushToGenderSetup()
-//        }
+        if !userInfo.isAvailable {
+            view.pushToGenderSetup()
+        }
     }
 }

+ 1 - 0
Lanu/Views/Profile/Mine/LNMineUserInfoView.swift

@@ -68,6 +68,7 @@ extension LNMineUserInfoView {
         container.addSubview(home)
         home.snp.makeConstraints { make in
             make.top.trailing.equalToSuperview()
+            make.size.equalTo(home.image?.size ?? .zero)
         }
         
         let avatar = buildAvatar()