public enum PlayerAction: Hashable, Sendable { case play, pause, mute, unmute, volumeUp, volumeDown case seekForward5s, seekBackward5s, seekForward10s, seekBackward10s case stepFrameForward, stepFrameBackward case enterFullscreen, exitFullscreen, enterCompact, exitCompact case enableAlwaysOnTop, disableAlwaysOnTop case openFile, openStream, closeFile, quit case showBufferInfo, hideBufferInfo case enableCompactSnapping, disableCompactSnapping case enableAutomaticQuality, disableAutomaticQuality case qualityDefault, quality(Int) static let namedActions: [(String, PlayerAction)] = [ ("play", .play), ("pause", .pause), ("mute", .mute), ("unmute", .unmute), ("volume_up", .volumeUp), ("volume_down", .volumeDown), ("seek_forward_5s", .seekForward5s), ("seek_backward_5s", .seekBackward5s), ("seek_forward_10s", .seekForward10s), ("seek_backward_10s", .seekBackward10s), ("step_frame_forward", .stepFrameForward), ("step_frame_backward", .stepFrameBackward), ("enter_fullscreen", .enterFullscreen), ("exit_fullscreen", .exitFullscreen), ("enter_compact", .enterCompact), ("exit_compact", .exitCompact), ("enable_always_on_top", .enableAlwaysOnTop), ("disable_always_on_top", .disableAlwaysOnTop), ("open_file", .openFile), ("open_stream", .openStream), ("close_file", .closeFile), ("quit", .quit), ("show_buffer_info", .showBufferInfo), ("hide_buffer_info", .hideBufferInfo), ("enable_compact_snapping", .enableCompactSnapping), ("disable_compact_snapping", .disableCompactSnapping), ("enable_automatic_quality", .enableAutomaticQuality), ("disable_automatic_quality", .disableAutomaticQuality), ("quality_default", .qualityDefault) ] private static let actionsByName = Dictionary(uniqueKeysWithValues: namedActions) private static let namesByAction = Dictionary(uniqueKeysWithValues: namedActions.map { ($0.1, $0.0) }) public init?(name: String) { if let action = Self.actionsByName[name] { self = action } else if name.hasPrefix("quality_"), name.hasSuffix("p") { let digits = name.dropFirst("quality_".count).dropLast() guard !digits.isEmpty, digits.allSatisfy({ $0 >= "0" && $0 <= "9" }), let height = Int(digits), height > 0, String(height) == digits else { return nil } self = .quality(height) } else { return nil } } public var name: String { if case .quality(let height) = self { return "quality_\(height)p" } return Self.namesByAction[self]! } public var inverse: PlayerAction? { switch self { case .play: return .pause case .pause: return .play case .mute: return .unmute case .unmute: return .mute case .enterFullscreen: return .exitFullscreen case .exitFullscreen: return .enterFullscreen case .enterCompact: return .exitCompact case .exitCompact: return .enterCompact case .enableAlwaysOnTop: return .disableAlwaysOnTop case .disableAlwaysOnTop: return .enableAlwaysOnTop case .showBufferInfo: return .hideBufferInfo case .hideBufferInfo: return .showBufferInfo case .enableCompactSnapping: return .disableCompactSnapping case .disableCompactSnapping: return .enableCompactSnapping case .enableAutomaticQuality: return .disableAutomaticQuality case .disableAutomaticQuality: return .enableAutomaticQuality default: return nil } } public var allowsRepeat: Bool { switch self { case .volumeUp, .volumeDown, .seekForward5s, .seekBackward5s, .seekForward10s, .seekBackward10s, .stepFrameForward, .stepFrameBackward: return true default: return false } } }