FAQ - MKS2508/MKS-IPTV-App GitHub Wiki

<style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.6; color: #333; max-width: 960px; margin: 0 auto; padding: 20px; } h1, h2, h3, h4, h5, h6 { font-weight: 600; color: #111; } a { color: #0366d6; text-decoration: none; } a:hover { text-decoration: underline; } code { font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; background-color: #f6f8fa; padding: .2em .4em; margin: 0; font-size: 85%; border-radius: 3px; } pre { background-color: #f6f8fa; padding: 16px; overflow: auto; line-height: 1.45; border-radius: 3px; } pre code { padding: 0; margin: 0; font-size: 100%; background-color: transparent; border: 0; } footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; font-size: 12px; color: #586069; } </style>

Home > Support > FAQ

❓ Frequently Asked Questions

Technical FAQ for developers and advanced users of MKS-IPTV-App.


🏗️ Architecture & Design

Q: Why use Actor-based concurrency instead of traditional queues?

A: Swift 6's Actor model provides several advantages for IPTV streaming:

  • Data race safety: Actors prevent concurrent access to mutable state
  • Structured concurrency: async/await provides cleaner error handling
  • Performance: Actor isolation reduces lock contention
  • Debugging: Easier to reason about concurrent operations
// Traditional approach (prone to data races)
class StreamManager {
    private var streams: [UUID: AVPlayer] = [:]
    private let queue = DispatchQueue(label: "streams")
    
    func addStream(_ id: UUID, player: AVPlayer) {
        queue.async {
            self.streams[id] = player // Potential race condition
        }
    }
}

// Actor-based approach (data race safe)
actor StreamManager {
    private var streams: [UUID: AVPlayer] = [:]
    
    func addStream(_ id: UUID, player: AVPlayer) {
        streams[id] = player // Compiler-enforced safety
    }
}

Q: How does the HTTP proxy improve streaming reliability?

A: The HTTP proxy solves several IPTV-specific challenges:

  1. Header manipulation: Many IPTV services require specific User-Agent or Referrer headers
  2. CORS issues: Bypasses cross-origin restrictions for web-based players
  3. URL obfuscation: Handles complex authentication and token-based URLs
  4. Protocol bridging: Converts HTTP streams to formats compatible with AVPlayer
// Example: Stream requires VLC User-Agent
let originalURL = "http://example.com/stream.m3u8"
let proxiedURL = "http://127.0.0.1:8080/proxy"

// Proxy adds required headers automatically
request.setValue("VLC/3.0.0", forHTTPHeaderField: "User-Agent")
request.setValue("http://example.com", forHTTPHeaderField: "Referrer")

Q: Why convert to MOV instead of keeping original format?

A: MOV conversion provides Apple ecosystem optimizations:

  • AirPlay compatibility: Better streaming to Apple TV
  • Metadata support: Enhanced metadata for Apple's media frameworks
  • Seek performance: Optimized for Apple's video players
  • Codec optimization: Uses Apple's hardware-accelerated codecs

🌐 Networking & Streaming

Q: How do you handle stream interruptions and recovery?

A: The StreamManager implements a multi-layered recovery strategy:

actor StreamRecoveryManager {
    enum RecoveryStrategy {
        case seekToLive        // For live streams
        case reloadPlayerItem  // For temporary network issues
        case completeRestart   // For persistent failures
    }
    
    func attemptRecovery(for sessionId: UUID) async {
        // Try strategies in order of increasing severity
        for strategy in [.seekToLive, .reloadPlayerItem, .completeRestart] {
            if await tryRecovery(strategy, for: sessionId) {
                break
            }
        }
    }
}

Recovery mechanisms:

  1. Seek to live edge: For live streams that fall behind
  2. Player item reload: For temporary network hiccups
  3. Complete restart: For persistent failures
  4. URL re-resolution: For expired or changed stream URLs

Q: How do you determine if a stream URL needs proxying?

A: The system uses several heuristics to determine proxy necessity:

func requiresProxy(_ url: URL) -> Bool {
    // Check for HTTP (vs HTTPS) - often needs headers
    if url.scheme == "http" {
        return true
    }
    
    // Check for known IPTV providers that require headers
    let iptvProviders = ["example.tv", "stream.provider"]
    if iptvProviders.contains(where: { url.host?.contains($0) == true }) {
        return true
    }
    
    // Check for query parameters indicating auth tokens
    if url.query?.contains("token") == true {
        return true
    }
    
    // Check for non-standard ports
    if let port = url.port, ![80, 443, 8080].contains(port) {
        return true
    }
    
    return false
}

Q: How do you handle different stream formats (HLS, DASH, direct MP4)?

A: The app adapts to different formats through format detection and appropriate handling:

enum StreamFormat {
    case hls        // .m3u8 files
    case dash       // .mpd files  
    case directMP4  // Direct video files
    case unknown
    
    static func detect(from url: URL) -> StreamFormat {
        let pathExtension = url.pathExtension.lowercased()
        
        switch pathExtension {
        case "m3u8": return .hls
        case "mpd": return .dash
        case "mp4", "mov", "avi", "mkv": return .directMP4
        default: return .unknown
        }
    }
    
    var requiresProxy: Bool {
        switch self {
        case .hls, .dash: return true  // Often need custom headers
        case .directMP4: return false  // Usually work directly
        case .unknown: return true     // Safe default
        }
    }
}

📥 Download System

Q: How do you handle resume/pause for large downloads?

A: The download system uses URLSession's built-in resume capabilities with persistent state:

actor DownloadManager {
    func pauseDownload(_ downloadId: UUID) async {
        guard let task = activeDownloads[downloadId] else { return }
        
        // Cancel and get resume data
        task.urlSessionTask?.cancel { resumeData in
            Task {
                await self.saveResumeData(downloadId, data: resumeData)
            }
        }
        
        // Update status
        task.status = .paused
        await saveDownloadState()
    }
    
    func resumeDownload(_ downloadId: UUID) async throws {
        guard let task = activeDownloads[downloadId],
              let resumeData = await getResumeData(downloadId) else {
            throw DownloadError.cannotResume
        }
        
        // Create new task with resume data
        let newTask = downloadSession.downloadTask(withResumeData: resumeData)
        task.urlSessionTask = newTask
        task.status = .downloading
        
        newTask.resume()
    }
}

Q: Why run a local HTTP server for downloaded content?

A: The local HTTP server provides several benefits for downloaded content:

  1. Streaming large files: Avoid loading entire files into memory
  2. Seek support: Enable video scrubbing and chapter navigation
  3. Range requests: Efficient partial content delivery
  4. Format agnostic: Works with any video format
  5. AVPlayer compatibility: Standard HTTP interface that AVPlayer expects
// Stream downloaded file through local server
let downloadedFile = "/path/to/downloaded/video.mp4"
let streamingURL = "http://127.0.0.1:8080/\(downloadId)"

// AVPlayer can now stream the local file with seek support
let player = AVPlayer(url: streamingURL)

Q: How do you ensure download integrity?

A: Multiple integrity checks are performed:

struct DownloadIntegrityChecker {
    func verifyDownload(_ downloadId: UUID) async throws {
        guard let task = await getDownloadTask(downloadId) else {
            throw DownloadError.taskNotFound
        }
        
        // 1. File size verification
        let expectedSize = task.totalBytes
        let actualSize = try getFileSize(task.localPath)
        guard expectedSize == actualSize else {
            throw DownloadError.sizeMismatch
        }
        
        // 2. File format validation
        let asset = AVAsset(url: task.localPath)
        let isReadable = try await asset.load(.isReadable)
        guard isReadable else {
            throw DownloadError.corruptFile
        }
        
        // 3. Checksum verification (if available)
        if let expectedHash = task.expectedChecksum {
            let actualHash = try await calculateChecksum(task.localPath)
            guard expectedHash == actualHash else {
                throw DownloadError.checksumMismatch
            }
        }
    }
}

🎨 UI & Platform Integration

Q: How do you handle different screen sizes and orientations?

A: The app uses SwiftUI's adaptive layout system with custom view modifiers:

struct AdaptiveContentView: View {
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    @Environment(\.verticalSizeClass) var verticalSizeClass
    
    var body: some View {
        Group {
            if horizontalSizeClass == .compact {
                // iPhone portrait: Stack layout
                VStack {
                    VideoPlayerView()
                    ControlsView()
                }
            } else {
                // iPad/iPhone landscape: Side-by-side
                HStack {
                    VideoPlayerView()
                    ControlsView()
                }
            }
        }
        .adaptToDeviceOrientation()
    }
}

extension View {
    func adaptToDeviceOrientation() -> some View {
        self.modifier(OrientationAdaptiveModifier())
    }
}

Q: How does TouchBar integration work on macOS?

A: TouchBar integration uses AppKit's NSTouchBar APIs bridged to SwiftUI:

class TouchBarManager: NSObject, NSTouchBarDelegate {
    @MainActor
    static func configureTouchBar(for window: NSWindow) {
        let touchBar = NSTouchBar()
        touchBar.delegate = TouchBarManager()
        touchBar.defaultItemIdentifiers = [
            .playPause,
            .volume,
            .search,
            .flexibleSpace,
            .categories
        ]
        
        window.touchBar = touchBar
    }
    
    func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
        switch identifier {
        case .playPause:
            return createPlayPauseItem()
        case .volume:
            return createVolumeSlider()
        case .search:
            return createSearchField()
        default:
            return nil
        }
    }
}

Q: How do you implement focus management for tvOS?

A: tvOS focus management uses focus guides and preferredFocusEnvironments:

struct tvOSContentView: View {
    @FocusState private var focusedItem: FocusableItem?
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns) {
                ForEach(items) { item in
                    ItemView(item: item)
                        .focused($focusedItem, equals: .item(item.id))
                        .focusable()
                }
            }
        }
        .focusGuide(.automatic)
        .onPlayPauseCommand {
            handlePlayPause()
        }
        .onMenuPressedCommand {
            handleMenuPress()
        }
    }
    
    enum FocusableItem: Hashable {
        case item(UUID)
        case searchField
        case settings
    }
}

🔧 Development & Testing

Q: How do you test Actor-based code?

A: Testing actors requires careful consideration of isolation:

@MainActor
class StreamManagerTests: XCTestCase {
    var streamManager: StreamManager!
    
    override func setUp() async throws {
        streamManager = StreamManager()
    }
    
    func testStreamLoading() async throws {
        // Test actor methods with async/await
        let testURL = URL(string: "https://test.stream")!
        let sessionId = try await streamManager.loadStream(testURL)
        
        // Verify results
        let session = await streamManager.getSession(sessionId)
        XCTAssertNotNil(session)
    }
    
    func testConcurrentAccess() async throws {
        // Test thread safety with concurrent operations
        let urls = (0..<10).map { URL(string: "https://test\($0).stream")! }
        
        // Start multiple streams concurrently
        let sessionIds = try await withThrowingTaskGroup(of: UUID.self) { group in
            for url in urls {
                group.addTask {
                    try await self.streamManager.loadStream(url)
                }
            }
            
            var results: [UUID] = []
            for try await sessionId in group {
                results.append(sessionId)
            }
            return results
        }
        
        XCTAssertEqual(sessionIds.count, urls.count)
    }
}

Q: How do you mock network requests for testing?

A: The app uses protocol-based dependency injection for testability:

protocol NetworkService {
    func fetchData(from url: URL) async throws -> Data
}

class ProductionNetworkService: NetworkService {
    func fetchData(from url: URL) async throws -> Data {
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
}

class MockNetworkService: NetworkService {
    var mockData: Data?
    var shouldThrowError = false
    
    func fetchData(from url: URL) async throws -> Data {
        if shouldThrowError {
            throw URLError(.networkConnectionLost)
        }
        return mockData ?? Data()
    }
}

// In tests
class StreamManagerTests: XCTestCase {
    func testWithMockNetwork() async throws {
        let mockNetwork = MockNetworkService()
        mockNetwork.mockData = "test stream data".data(using: .utf8)
        
        let streamManager = StreamManager(networkService: mockNetwork)
        // Test with predictable mock data
    }
}

Q: How do you debug memory issues in a SwiftUI + Actor app?

A: Use a combination of Instruments and custom memory tracking:

#if DEBUG
class MemoryTracker: ObservableObject {
    @Published var allocatedActors: [String: Int] = [:]
    @Published var allocatedViews: [String: Int] = [:]
    
    private let queue = DispatchQueue(label: "memory.tracker")
    
    func trackActor<T>(_ actor: T) {
        let typeName = String(describing: type(of: actor))
        queue.async {
            self.allocatedActors[typeName, default: 0] += 1
            DispatchQueue.main.async {
                self.objectWillChange.send()
            }
        }
    }
    
    func trackView<T: View>(_ view: T) {
        let typeName = String(describing: type(of: view))
        queue.async {
            self.allocatedViews[typeName, default: 0] += 1
            DispatchQueue.main.async {
                self.objectWillChange.send()
            }
        }
    }
    
    var totalMemoryUsage: String {
        let info = mach_task_basic_info()
        var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
        
        let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
            $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
                task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
            }
        }
        
        if kerr == KERN_SUCCESS {
            let memory = Float(info.resident_size) / 1024.0 / 1024.0
            return String(format: "%.1f MB", memory)
        }
        
        return "Unknown"
    }
}

// Usage in SwiftUI views
struct DebugMemoryView: View {
    @StateObject private var memoryTracker = MemoryTracker()
    
    var body: some View {
        VStack {
            Text("Memory: \(memoryTracker.totalMemoryUsage)")
            List {
                Section("Actors") {
                    ForEach(memoryTracker.allocatedActors.sorted(by: <), id: \.key) { key, value in
                        Text("\(key): \(value)")
                    }
                }
                Section("Views") {
                    ForEach(memoryTracker.allocatedViews.sorted(by: <), id: \.key) { key, value in
                        Text("\(key): \(value)")
                    }
                }
            }
        }
    }
}
#endif

🚀 Performance & Optimization

Q: How do you optimize for different device capabilities?

A: The app dynamically adapts based on device capabilities:

actor DeviceCapabilityManager {
    struct DeviceCapabilities {
        let maxConcurrentDownloads: Int
        let maxVideoResolution: CGSize
        let supportsHardwareDecoding: Bool
        let maxBufferSize: TimeInterval
    }
    
    static func detectCapabilities() -> DeviceCapabilities {
        let device = UIDevice.current
        let processInfo = ProcessInfo.processInfo
        
        // Determine based on device model and available memory
        let totalMemory = processInfo.physicalMemory
        let deviceModel = device.model
        
        switch (deviceModel, totalMemory) {
        case (_, let memory) where memory >= 8_000_000_000: // 8GB+
            return DeviceCapabilities(
                maxConcurrentDownloads: 4,
                maxVideoResolution: CGSize(width: 3840, height: 2160), // 4K
                supportsHardwareDecoding: true,
                maxBufferSize: 60.0
            )
        case (_, let memory) where memory >= 4_000_000_000: // 4GB+
            return DeviceCapabilities(
                maxConcurrentDownloads: 3,
                maxVideoResolution: CGSize(width: 1920, height: 1080), // 1080p
                supportsHardwareDecoding: true,
                maxBufferSize: 30.0
            )
        default: // Lower-end devices
            return DeviceCapabilities(
                maxConcurrentDownloads: 2,
                maxVideoResolution: CGSize(width: 1280, height: 720), // 720p
                supportsHardwareDecoding: false,
                maxBufferSize: 15.0
            )
        }
    }
}

Q: How do you handle memory pressure during video playback?

A: The app implements proactive memory management:

actor MemoryPressureManager {
    private var memoryPressureSource: DispatchSourceMemoryPressure?
    
    func startMonitoring() {
        memoryPressureSource = DispatchSource.makeMemoryPressureSource(
            eventMask: [.warning, .critical],
            queue: .global()
        )
        
        memoryPressureSource?.setEventHandler { [weak self] in
            Task {
                await self?.handleMemoryPressure()
            }
        }
        
        memoryPressureSource?.resume()
    }
    
    private func handleMemoryPressure() async {
        // Stop non-essential downloads
        await DownloadManager.shared.pauseNonEssentialDownloads()
        
        // Reduce video buffer size
        await StreamManager.shared.reduceBufferSizes()
        
        // Clear caches
        URLCache.shared.removeAllCachedResponses()
        
        // Notify UI to update if needed
        await MainActor.run {
            NotificationCenter.default.post(
                name: .memoryPressureDetected,
                object: nil
            )
        }
    }
}

🔐 Security & Privacy

Q: How do you handle sensitive data like API keys and tokens?

A: The app uses secure storage and runtime protection:

actor SecureConfiguration {
    private let keychain = Keychain(service: "com.mks.iptv.secure")
    
    func storeAPIKey(_ key: String) async throws {
        try keychain.set(key, key: "api_key")
    }
    
    func getAPIKey() async throws -> String? {
        return try keychain.get("api_key")
    }
    
    // Runtime obfuscation for hardcoded values
    var userAgent: String {
        let encoded = "TUtTLUlQVFYtQXBwLzEuMA==" // Base64 encoded
        return String(data: Data(base64Encoded: encoded)!, encoding: .utf8)!
    }
}

Q: How do you prevent unauthorized access to downloaded content?

A: Downloaded files are protected through multiple layers:

actor ContentProtectionManager {
    func protectDownloadedFile(at path: URL) async throws {
        // 1. Set file attributes to prevent external access
        var resourceValues = URLResourceValues()
        resourceValues.isExcludedFromBackup = true
        try path.setResourceValues(resourceValues)
        
        // 2. Encrypt sensitive content (optional)
        if shouldEncryptContent {
            try await encryptFile(at: path)
        }
        
        // 3. Set file permissions (macOS)
        #if os(macOS)
        try FileManager.default.setAttributes(
            [.posixPermissions: 0o600], // Owner read/write only
            ofItemAtPath: path.path
        )
        #endif
    }
    
    private func encryptFile(at path: URL) async throws {
        // Implementation depends on encryption requirements
        // Could use CryptoKit for local encryption
    }
}

Remember: This FAQ covers technical implementation details. For user-facing questions, refer to the main documentation at MKS2508.github.io/MKS-IPTV-App.


{{< include _Footer.md >}}

⚠️ **GitHub.com Fallback** ⚠️