MiniPlayer/Sources/HLSRecorder.swift
yumoqing d74543e3ed fix(iOS): simplify recorder to local file only, add app icon
- Reverted broken AVPlayer capture code back to simple local file copy + URLSession download
- Stream recording shows clear error message recommending local files
- Added Assets.car compilation and CFBundleIcons to Info.plist for app icon
- Stopped wasting time on iOS stream capture — needs ffmpeg iOS static lib
2026-07-17 21:55:49 +08:00

109 lines
4.3 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#if os(iOS)
import Foundation
import AVFoundation
import UIKit
/// iOS CBS DRM ffmpeg iOS
@MainActor
final class HLSRecorder: ObservableObject {
@Published var isRecording = false
@Published var durationText = "00:00"
var onRecordingSaved: ((URL) -> Void)?
var onError: ((String) -> Void)?
private var recordTask: Task<Void, Never>?
private var startTime: Date?
private var timer: Timer?
func startRecording(url: URL, player: AVPlayer) {
guard !isRecording else { return }
isRecording = true
startTime = Date()
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
Task { @MainActor in
guard let self, let start = self.startTime else { return }
let elapsed = Int(Date().timeIntervalSince(start))
self.durationText = String(format: "%02d:%02d:%02d", elapsed / 3600, (elapsed % 3600) / 60, elapsed % 60)
}
}
recordTask = Task { [weak self] in
guard let self else { return }
do {
let outputURL: URL
if url.isFileURL {
outputURL = try await self.copyLocalFile(url: url)
} else {
// iOS HLS
// CBS DRM/CDN ffmpeg iOS
// URLSession HTTP
outputURL = try await self.downloadFile(url: url)
}
let attrs = try? FileManager.default.attributesOfItem(atPath: outputURL.path)
let size = (attrs?[.size] as? Int64) ?? 0
NSLog("[MiniPlayer] Recording complete: %@ (%lld bytes)", outputURL.path, size)
await MainActor.run {
self.isRecording = false
self.timer?.invalidate()
if size > 1024 {
self.onRecordingSaved?(outputURL)
} else {
self.onError?("Recording too small (\(size) bytes) — stream may be DRM-protected. Try recording a local file.")
try? FileManager.default.removeItem(at: outputURL)
}
}
} catch {
NSLog("[MiniPlayer] Recording error: %@", error.localizedDescription)
await MainActor.run {
self.isRecording = false
self.timer?.invalidate()
self.onError?(error.localizedDescription)
}
}
}
}
func stopRecording() {
guard isRecording else { return }
//
recordTask?.cancel()
isRecording = false
timer?.invalidate()
}
// MARK: -
private func copyLocalFile(url: URL) async throws -> URL {
let ext = url.pathExtension.isEmpty ? "mp4" : url.pathExtension
let outputURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("MiniPlayer_Recording_\(Int(Date().timeIntervalSince1970)).\(ext)")
if FileManager.default.fileExists(atPath: outputURL.path) {
try FileManager.default.removeItem(at: outputURL)
}
try FileManager.default.copyItem(at: url, to: outputURL)
return outputURL
}
// MARK: - HTTP
private func downloadFile(url: URL) async throws -> URL {
let ext = url.pathExtension.isEmpty ? "mp4" : url.pathExtension
let outputURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("MiniPlayer_Recording_\(Int(Date().timeIntervalSince1970)).\(ext)")
if FileManager.default.fileExists(atPath: outputURL.path) {
try FileManager.default.removeItem(at: outputURL)
}
let (tempURL, _) = try await URLSession.shared.download(from: url)
try FileManager.default.moveItem(at: tempURL, to: outputURL)
return outputURL
}
}
#endif