Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

here have a crash, the version is 2.0.1 #26

Open
Enjoyworld-AI opened this issue Jan 16, 2025 · 5 comments
Open

here have a crash, the version is 2.0.1 #26

Enjoyworld-AI opened this issue Jan 16, 2025 · 5 comments

Comments

@Enjoyworld-AI
Copy link

image

@sukov
Copy link
Owner

sukov commented Jan 16, 2025

Please provide example code which triggers this crash so that I can investigate further.

@Enjoyworld-AI
Copy link
Author

Please provide example code which triggers this crash so that I can investigate further.

I can’t simulate the crash right now, nor have I tested it. These crashes all occurred for online users.

@Enjoyworld-AI
Copy link
Author

Please provide example code which triggers this crash so that I can investigate further.

import CachingPlayerItem
import HFAudioPlayer
import HFConfig
import HFNetworking
import HFUIKit
import Foundation

enum ResourcePreloadItemType: String, Codable {
case image
case video
}

struct ResourcePreloadItemContent: Codable {
let cacheName: String?
var url: String {
(ConfigManager.shared.configInfo?.fileServiceDomain ?? kDefaultServiceDomain) + "/" + (filePath ?? "")
}
let filePath: String?
}

struct ResourcePreloadItem: Codable {
let contentType: ResourcePreloadItemType?
let fileUrlList: [ResourcePreloadItemContent]?
}

struct ResourcePreloadModel: Codable {
let currentVersion: Int?
let contentList: [ResourcePreloadItem]?
}

struct ResourcePreloadRequest: APIRequest {
typealias ResponseModel = ResourcePreloadModel

var path: String { "/resource/preloadV2" }

var version: Int?

var parameters: [String: Any]? {
    if let version = version {
        return ["version": version]
    }
    return nil
}

}

public class ResourcePreloadManager {
static let shared = ResourcePreloadManager()

let versionSaveKey: String = "ResourcePreloadVersionSaveKey"

var needLoadImage: Bool = false
var imageLoadSuccess: Bool = false

var needLoadVideo: Bool = false
var videoLoadSuccess: Bool = false
var totalVideoCount: Int = 0
var successVideoCount: Int = 0
var saveLoadIingUrl: [URL] = []

var resourceVersion: Int?

var hasFetched: Bool = false
var cachingPlayerItems: [CachingPlayerItem] = []

var cacheImages: [ResourcePreloadItemContent] = []

@objc
public func preloadResource() {
    if hasFetched {
        return
    }
    self.hasFetched = true

    var version: Int?
    let current = UserDefaults.standard.integer(forKey: versionSaveKey)
    if current != 0 {
        version = current
    }
    NotificationCenter.default.removeObserver(self, name: .networkReachabilityDidChange, object: nil)
    if !NetworkCenter.shared.isNetworkReachable {
        NotificationCenter.default.addObserver(self, selector: #selector(preloadResource), name: .networkReachabilityDidChange, object: nil)
        return
    }

    ResourcePreloadRequest(version: version).start { result in
        switch result {
        case .success(let model):
            self.handleResource(resource: model)
        default:
            break
        }
    }

    self.loadCacheImages()
}

private func handleResource(resource: ResourcePreloadModel) {
    if resource.contentList?.isEmpty ?? true {
        return
    }
    self.resourceVersion = resource.currentVersion

    resource.contentList?.forEach({ item in
        switch item.contentType {
        case .image:
            self.handleImageList(item: item)
        case .video:
            self.handleVideoList(item: item)
        default:
            break
        }
    })
}

private func loadCacheImages() {
    if let data = UserDefaults.standard.data(forKey: "images_preload_enjoy") {
        let decoder = JSONDecoder()
        do {
            let model = try decoder.decode([ResourcePreloadItemContent].self, from: data)
            self.cacheImages = model
        } catch {
            print("Unable to Decode ResourcePreloadItemContent (\(error))")
        }
    }
}

private func saveCacheImages() {
    if self.cacheImages.isEmpty {
        return
    }
    let encoder = JSONEncoder()
    do {
        let data = try encoder.encode(self.cacheImages)
        UserDefaults.standard.set(data, forKey: "images_preload_enjoy")
    } catch {
        print("Unable to Encode ResourcePreloadItemContent (\(error))")
    }
}

private func checkSaveVersion() {
    var imageLoadFinish = false
    if (needLoadImage && imageLoadSuccess) || needLoadImage == false {
        imageLoadFinish = true
    }

    var videoLoadFinish = false
    if (needLoadVideo && videoLoadSuccess) || needLoadVideo == false {
        videoLoadFinish = true
    }

    if imageLoadFinish && videoLoadFinish {
        UserDefaults.standard.set(self.resourceVersion, forKey: self.versionSaveKey)
    }
}

private func localPath(for url: URL) -> URL {
    return AudioPlayer().localPath(for: url)
}
private func isFileDownloadCompleted(atPath filePath: String) -> Bool {
    return AudioPlayer().isFileDownloadCompleted(atPath: filePath)
}
public func preloadVoiceData(url: URL) {
    let localUrl = localPath(for: url)
    if saveLoadIingUrl.contains(url) {
        return
    }
    if FileManager.default.fileExists(atPath: localUrl.path) && isFileDownloadCompleted(atPath: localUrl.path) {
        return
    } else {
        let dir = (localUrl.path as NSString).deletingLastPathComponent
        var isDir: ObjCBool = false
        do {
            let fileAttributes = try FileManager.default.attributesOfItem(atPath: localUrl.path)
            if let fileSize = fileAttributes[.size] as? Int64, fileSize >= 10 {
                do {
                    try FileManager.default.removeItem(atPath: localUrl.path)
                    if let index = saveLoadIingUrl.firstIndex(of: url) {
                        saveLoadIingUrl.remove(at: index)
                    }
                    Log.local(.common, message: "Deleted local file as it was partially downloaded.")
                } catch {
                    Log.error(error)
                }
            }
        } catch {
            Log.local(.common, message: "Error getting file attributes: \(error)")
        }
        if !FileManager.default.fileExists(atPath: dir, isDirectory: &isDir) || !isDir.boolValue {
            do {
                try? FileManager.default.removeItem(atPath: dir)
                try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true)
            } catch {
                Log.error(error)
            }
        }

        let item = CachingPlayerItem(url: url, saveFilePath: localUrl.path, customFileExtension: nil)
        item.delegate = self
        item.download()
        saveLoadIingUrl.append(url)
        cachingPlayerItems.append(item)
    }
}

}

extension ResourcePreloadManager {
private func handleImageList(item: ResourcePreloadItem) {
guard let list = item.fileUrlList, !list.isEmpty else {
return
}
self.reloadImagesCache(list)

    var images: [URL] = []
    for content in list {
        if let url = URL(string: content.url ?? "") {
            images.append(url)
        }
    }

    self.needLoadImage = true
    self.imageLoadSuccess = false
    ImagePrefetcher.shared.prefetcher.prefetchURLs(images, options: [.allowInvalidSSLCertificates, .avoidAutoSetImage, .continueInBackground, .decodeFirstFrameOnly, .lowPriority, .decodeFirstFrameOnly], context: SDWebImageContext) { _, _ in
    } completed: { _, noOfSkipped in
        if noOfSkipped == 0 {
            self.imageLoadSuccess = true
            self.checkSaveVersion()
        }
    }
}

private func reloadImagesCache(_ items: [ResourcePreloadItemContent]) {
    var combinedDict = [String: ResourcePreloadItemContent]()

    for item in self.cacheImages {
        if let cacheName = item.cacheName {
            combinedDict[cacheName] = item
        }
    }

    for item in items {
        if let cacheName = item.cacheName {
            combinedDict[cacheName] = item
        }
    }

    let combinedArray = Array(combinedDict.values)
    self.cacheImages = combinedArray
    self.saveCacheImages()
}

private func handleVideoList(item: ResourcePreloadItem) {
    guard let list = item.fileUrlList, !list.isEmpty else {
        return
    }

    self.needLoadVideo = true
    self.videoLoadSuccess = false
    self.totalVideoCount = list.count
    list.forEach { content in
        self.downloadVideoResource(content: content)
    }
}

}

extension ResourcePreloadManager {
private func downloadVideoResource(content: ResourcePreloadItemContent) {
guard let url = URL(string: content.url ?? ""), let name = content.cacheName else {
return
}

    let key = name
    if DownloadResourceCenter.shared.resourceCacheOfType(.video, key: key) != nil {
        self.downloadFinish()
        return
    }

    let dir = DownloadResourceCenter.shared.videoConfig.storePath
    let filePath = (dir as NSString).appendingPathComponent(url.lastPathComponent)
    DownloadCenter.shared.downloadVideo(url: content.url ?? "", saveUrl: filePath) { result in
        switch result {
        case .success:
            self.downloadFinish()
            DownloadResourceCenter.shared.saveFilePath(.video, value: filePath, key: key)
        case .failure:
            break
        }
    }
}

private func downloadFinish() {
    self.successVideoCount += 1
    if self.successVideoCount == self.totalVideoCount {
        self.videoLoadSuccess = true
        self.checkSaveVersion()
    }
}

}
extension ResourcePreloadManager: CachingPlayerItemDelegate {
/// Called when the media file is fully downloaded.
@objc public func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingFileAt filePath: String) {
AudioPlayer().markFileDownloadCompleted(atPath: filePath)
if let index = cachingPlayerItems.firstIndex(of: playerItem) {
cachingPlayerItems.remove(at: index)
}
}

/// Called every time a new portion of data is received.
@objc public func playerItem(_ playerItem: CachingPlayerItem, didDownloadBytesSoFar bytesDownloaded: Int, outOf bytesExpected: Int) {
}

/// Called on downloading error.
@objc public func playerItem(_ playerItem: CachingPlayerItem, downloadingFailedWith error: Error) {
    Log.error(error)
}

// MARK: Playing delegate methods

/// Called after initial prebuffering is finished, means we are ready to play.
@objc public func playerItemReadyToPlay(_ playerItem: CachingPlayerItem) {
}

/// Called when the player is unable to play the data/url.
@objc public func playerItemDidFailToPlay(_ playerItem: CachingPlayerItem, withError error: Error?) {
    if let error = error {
        Log.error(error)
    }
}

/// Called when the data being downloaded did not arrive in time to continue playback.
@objc public func playerItemPlaybackStalled(_ playerItem: CachingPlayerItem) {
}

}

extension ResourcePreloadManager {
func fetchCacheImageUrl(_ cacheName: String) -> String? {
if self.cacheImages.isEmpty {
return nil
}
var cacheUrl: String?
for item in self.cacheImages {
if item.cacheName == cacheName {
cacheUrl = item.url
break
}
}
return cacheUrl
}
}

@Enjoyworld-AI
Copy link
Author

@sukov

@sukov
Copy link
Owner

sukov commented Jan 23, 2025

Thank you for providing some code. Unfortunately it didn't help me reproduce the crash.

One thing that you can try is replacing the line #220 on CachingPlayerItem.deinit with resourceLoaderDelegate.invalidateAndCancelSession(shouldResetData: false). Please let me know if this fixes the issue.

Another thing you could possible try to help me fix this is adding Crashlytics Logs on ResourceLoaderDelegate, CachingPlayerItem and PendingRequest.

@Enjoyworld-AI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants