Files
Pocket-Poster/Pocket Poster/Packages/GIFImage/Sources/domain/PresentationController.swift
T
2025-07-19 08:58:08 -04:00

66 lines
2.0 KiB
Swift

//
// File.swift
//
//
// Created by Igor Ferreira on 9/9/22.
//
import Foundation
import SwiftUI
private let kDefaultGIFFrameInterval: TimeInterval = 1.0 / 24.0
struct PresentationController {
let source: GIFSource
let frameRate: FrameRate
let action: (GIFSource) async throws -> Void
@Binding var animate: Bool
@Binding var loop: Bool
init(
source: GIFSource,
frameRate: FrameRate,
animate: Binding<Bool>,
loop: Binding<Bool>,
action: @Sendable @escaping (GIFSource) async throws -> Void = { _ in }
) {
self.source = source
self.action = action
self.frameRate = frameRate
self._animate = animate
self._loop = loop
}
func start(imageLoader: ImageLoader, fallbackImage: RawImage, frameUpdate: (RawImage) async -> Void) async {
do {
repeat {
for try await imageFrame in try await imageLoader.load(source: source) {
try await update(imageFrame, frameUpdate: frameUpdate)
if !animate { break }
}
try await action(source)
} while(self.loop && self.animate)
} catch {
if !(error is CancellationError) {
await frameUpdate(fallbackImage)
}
}
}
private func update(_ imageFrame: ImageFrame, frameUpdate: (RawImage) async -> Void) async throws {
await frameUpdate(RawImage.create(with: imageFrame.image))
let calculatedInterval = imageFrame.interval ?? kDefaultGIFFrameInterval
let interval: Double
switch frameRate {
case .static(let fps):
interval = (1.0 / Double(fps))
case .limited(let fps):
let intervalLimit = (1.0 / Double(fps))
interval = max(calculatedInterval, intervalLimit)
case .dynamic:
interval = imageFrame.interval ?? kDefaultGIFFrameInterval
}
try await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000.0))
}
}