diff --git a/DivKit/DivKitInfo.swift b/DivKit/DivKitInfo.swift index a82a2942..6810bebd 100644 --- a/DivKit/DivKitInfo.swift +++ b/DivKit/DivKitInfo.swift @@ -1,3 +1,3 @@ public enum DivKitInfo { - public static let version = "25.0.0" + public static let version = "25.1.0" } diff --git a/DivKit/Extensions/DivVideoExtensions.swift b/DivKit/Extensions/DivVideoExtensions.swift index 4b8826cd..878b19f5 100644 --- a/DivKit/Extensions/DivVideoExtensions.swift +++ b/DivKit/Extensions/DivVideoExtensions.swift @@ -26,7 +26,7 @@ extension DivVideo: DivBlockModeling { Binding(context: context, name: $0) } let preview: Image? = resolvePreview(resolver).flatMap(_makeImage(base64:)) - let videoData = videoData.makeVideoData(resolver: resolver) + let videoData = VideoData(videos: videoSources.map { $0.makeVideo(resolver: resolver) }) let playbackConfig = PlaybackConfig( autoPlay: autostart, @@ -66,27 +66,18 @@ extension DivVideo: DivBlockModeling { } } -extension DivVideoData { - func makeVideoData(resolver: ExpressionResolver) -> VideoData { - switch self { - case let .divVideoDataStream(stream): - return .stream(stream.resolveUrl(resolver)!) - case let .divVideoDataVideo(videos): - return .video(videos.videoSources.map { - let resolution: CGSize? = $0.resolution.flatMap { resolution in - CGSize( - width: resolution.resolveWidth(resolver) ?? 0, - height: resolution.resolveHeight(resolver) ?? 0 - ) - } - return LayoutKit.Video( - url: $0.resolveUrl(resolver)!, - resolution: resolution ?? .zero, - codec: $0.resolveCodec(resolver), - mimeType: $0.resolveMimeType(resolver) - ) - }) +extension DivVideoSource { + func makeVideo(resolver: ExpressionResolver) -> Video { + let resolution: CGSize? = resolution.flatMap { resolution in + CGSize( + width: resolution.resolveWidth(resolver) ?? 0, + height: resolution.resolveHeight(resolver) ?? 0 + ) } + return Video(url: resolveUrl(resolver)!, + resolution: resolution, + bitrate: resolveBitrate(resolver).flatMap { Double($0) }, + mimeType: resolveMimeType(resolver)) } } diff --git a/DivKit/generated_sources/DivVideo.swift b/DivKit/generated_sources/DivVideo.swift index 706e1e8b..feaa176a 100644 --- a/DivKit/generated_sources/DivVideo.swift +++ b/DivKit/generated_sources/DivVideo.swift @@ -18,12 +18,14 @@ public final class DivVideo: DivBase { public let elapsedTimeVariable: String? // at least 1 char public let endActions: [DivAction]? // at least 1 elements public let extensions: [DivExtension]? // at least 1 elements + public let fatalActions: [DivAction]? // at least 1 elements public let focus: DivFocus? public let height: DivSize // default value: .divWrapContentSize(DivWrapContentSize()) public let id: String? // at least 1 char public let margins: DivEdgeInsets public let muted: Expression // default value: false public let paddings: DivEdgeInsets + public let pauseActions: [DivAction]? // at least 1 elements public let playerSettingsPayload: [String: Any]? public let preview: Expression? // at least 1 char public let repeatable: Expression // default value: false @@ -36,7 +38,7 @@ public final class DivVideo: DivBase { public let transitionIn: DivAppearanceTransition? public let transitionOut: DivAppearanceTransition? public let transitionTriggers: [DivTransitionTrigger]? // at least 1 elements - public let videoData: DivVideoData + public let videoSources: [DivVideoSource] // at least 1 elements public let visibility: Expression // default value: visible public let visibilityAction: DivVisibilityAction? public let visibilityActions: [DivVisibilityAction]? // at least 1 elements @@ -118,6 +120,9 @@ public final class DivVideo: DivBase { static let extensionsValidator: AnyArrayValueValidator = makeArrayValidator(minItems: 1) + static let fatalActionsValidator: AnyArrayValueValidator = + makeArrayValidator(minItems: 1) + static let focusValidator: AnyValueValidator = makeNoOpValueValidator() @@ -136,6 +141,9 @@ public final class DivVideo: DivBase { static let paddingsValidator: AnyValueValidator = makeNoOpValueValidator() + static let pauseActionsValidator: AnyArrayValueValidator = + makeArrayValidator(minItems: 1) + static let playerSettingsPayloadValidator: AnyValueValidator<[String: Any]> = makeNoOpValueValidator() @@ -172,6 +180,9 @@ public final class DivVideo: DivBase { static let transitionTriggersValidator: AnyArrayValueValidator = makeArrayValidator(minItems: 1) + static let videoSourcesValidator: AnyArrayValueValidator = + makeArrayValidator(minItems: 1) + static let visibilityValidator: AnyValueValidator = makeNoOpValueValidator() @@ -197,12 +208,14 @@ public final class DivVideo: DivBase { elapsedTimeVariable: String? = nil, endActions: [DivAction]? = nil, extensions: [DivExtension]? = nil, + fatalActions: [DivAction]? = nil, focus: DivFocus? = nil, height: DivSize? = nil, id: String? = nil, margins: DivEdgeInsets? = nil, muted: Expression? = nil, paddings: DivEdgeInsets? = nil, + pauseActions: [DivAction]? = nil, playerSettingsPayload: [String: Any]? = nil, preview: Expression? = nil, repeatable: Expression? = nil, @@ -215,7 +228,7 @@ public final class DivVideo: DivBase { transitionIn: DivAppearanceTransition? = nil, transitionOut: DivAppearanceTransition? = nil, transitionTriggers: [DivTransitionTrigger]? = nil, - videoData: DivVideoData, + videoSources: [DivVideoSource], visibility: Expression? = nil, visibilityAction: DivVisibilityAction? = nil, visibilityActions: [DivVisibilityAction]? = nil, @@ -233,12 +246,14 @@ public final class DivVideo: DivBase { self.elapsedTimeVariable = elapsedTimeVariable self.endActions = endActions self.extensions = extensions + self.fatalActions = fatalActions self.focus = focus self.height = height ?? .divWrapContentSize(DivWrapContentSize()) self.id = id self.margins = margins ?? DivEdgeInsets() self.muted = muted ?? .value(false) self.paddings = paddings ?? DivEdgeInsets() + self.pauseActions = pauseActions self.playerSettingsPayload = playerSettingsPayload self.preview = preview self.repeatable = repeatable ?? .value(false) @@ -251,7 +266,7 @@ public final class DivVideo: DivBase { self.transitionIn = transitionIn self.transitionOut = transitionOut self.transitionTriggers = transitionTriggers - self.videoData = videoData + self.videoSources = videoSources self.visibility = visibility ?? .value(.visible) self.visibilityAction = visibilityAction self.visibilityActions = visibilityActions @@ -292,55 +307,57 @@ extension DivVideo: Equatable { return false } guard + lhs.fatalActions == rhs.fatalActions, lhs.focus == rhs.focus, - lhs.height == rhs.height, - lhs.id == rhs.id + lhs.height == rhs.height else { return false } guard + lhs.id == rhs.id, lhs.margins == rhs.margins, - lhs.muted == rhs.muted, - lhs.paddings == rhs.paddings + lhs.muted == rhs.muted + else { + return false + } + guard + lhs.paddings == rhs.paddings, + lhs.pauseActions == rhs.pauseActions, + lhs.preview == rhs.preview else { return false } guard - lhs.preview == rhs.preview, lhs.repeatable == rhs.repeatable, - lhs.resumeActions == rhs.resumeActions + lhs.resumeActions == rhs.resumeActions, + lhs.rowSpan == rhs.rowSpan else { return false } guard - lhs.rowSpan == rhs.rowSpan, lhs.selectedActions == rhs.selectedActions, - lhs.tooltips == rhs.tooltips + lhs.tooltips == rhs.tooltips, + lhs.transform == rhs.transform else { return false } guard - lhs.transform == rhs.transform, lhs.transitionChange == rhs.transitionChange, - lhs.transitionIn == rhs.transitionIn + lhs.transitionIn == rhs.transitionIn, + lhs.transitionOut == rhs.transitionOut else { return false } guard - lhs.transitionOut == rhs.transitionOut, lhs.transitionTriggers == rhs.transitionTriggers, - lhs.videoData == rhs.videoData + lhs.videoSources == rhs.videoSources, + lhs.visibility == rhs.visibility else { return false } guard - lhs.visibility == rhs.visibility, lhs.visibilityAction == rhs.visibilityAction, - lhs.visibilityActions == rhs.visibilityActions - else { - return false - } - guard + lhs.visibilityActions == rhs.visibilityActions, lhs.width == rhs.width else { return false @@ -366,12 +383,14 @@ extension DivVideo: Serializable { result["elapsed_time_variable"] = elapsedTimeVariable result["end_actions"] = endActions?.map { $0.toDictionary() } result["extensions"] = extensions?.map { $0.toDictionary() } + result["fatal_actions"] = fatalActions?.map { $0.toDictionary() } result["focus"] = focus?.toDictionary() result["height"] = height.toDictionary() result["id"] = id result["margins"] = margins.toDictionary() result["muted"] = muted.toValidSerializationValue() result["paddings"] = paddings.toDictionary() + result["pause_actions"] = pauseActions?.map { $0.toDictionary() } result["player_settings_payload"] = playerSettingsPayload result["preview"] = preview?.toValidSerializationValue() result["repeatable"] = repeatable.toValidSerializationValue() @@ -384,7 +403,7 @@ extension DivVideo: Serializable { result["transition_in"] = transitionIn?.toDictionary() result["transition_out"] = transitionOut?.toDictionary() result["transition_triggers"] = transitionTriggers?.map { $0.rawValue } - result["video_data"] = videoData.toDictionary() + result["video_sources"] = videoSources.map { $0.toDictionary() } result["visibility"] = visibility.toValidSerializationValue() result["visibility_action"] = visibilityAction?.toDictionary() result["visibility_actions"] = visibilityActions?.map { $0.toDictionary() } diff --git a/DivKit/generated_sources/DivVideoData.swift b/DivKit/generated_sources/DivVideoData.swift deleted file mode 100644 index 0194ce6b..00000000 --- a/DivKit/generated_sources/DivVideoData.swift +++ /dev/null @@ -1,41 +0,0 @@ -// Generated code. Do not modify. - -import CommonCorePublic -import Foundation -import Serialization - -@frozen -public enum DivVideoData { - case divVideoDataVideo(DivVideoDataVideo) - case divVideoDataStream(DivVideoDataStream) - - public var value: Serializable { - switch self { - case let .divVideoDataVideo(value): - return value - case let .divVideoDataStream(value): - return value - } - } -} - -#if DEBUG -extension DivVideoData: Equatable { - public static func ==(lhs: DivVideoData, rhs: DivVideoData) -> Bool { - switch (lhs, rhs) { - case let (.divVideoDataVideo(l), .divVideoDataVideo(r)): - return l == r - case let (.divVideoDataStream(l), .divVideoDataStream(r)): - return l == r - default: - return false - } - } -} -#endif - -extension DivVideoData: Serializable { - public func toDictionary() -> [String: ValidSerializationValue] { - return value.toDictionary() - } -} diff --git a/DivKit/generated_sources/DivVideoDataStream.swift b/DivKit/generated_sources/DivVideoDataStream.swift deleted file mode 100644 index 9edb57f4..00000000 --- a/DivKit/generated_sources/DivVideoDataStream.swift +++ /dev/null @@ -1,42 +0,0 @@ -// Generated code. Do not modify. - -import CommonCorePublic -import Foundation -import Serialization - -public final class DivVideoDataStream { - public static let type: String = "stream" - public let url: Expression - - public func resolveUrl(_ resolver: ExpressionResolver) -> URL? { - resolver.resolveStringBasedValue(expression: url, initializer: URL.init(string:)) - } - - init( - url: Expression - ) { - self.url = url - } -} - -#if DEBUG -extension DivVideoDataStream: Equatable { - public static func ==(lhs: DivVideoDataStream, rhs: DivVideoDataStream) -> Bool { - guard - lhs.url == rhs.url - else { - return false - } - return true - } -} -#endif - -extension DivVideoDataStream: Serializable { - public func toDictionary() -> [String: ValidSerializationValue] { - var result: [String: ValidSerializationValue] = [:] - result["type"] = Self.type - result["url"] = url.toValidSerializationValue() - return result - } -} diff --git a/DivKit/generated_sources/DivVideoDataStreamTemplate.swift b/DivKit/generated_sources/DivVideoDataStreamTemplate.swift deleted file mode 100644 index e2b590a3..00000000 --- a/DivKit/generated_sources/DivVideoDataStreamTemplate.swift +++ /dev/null @@ -1,100 +0,0 @@ -// Generated code. Do not modify. - -import CommonCorePublic -import Foundation -import Serialization - -public final class DivVideoDataStreamTemplate: TemplateValue { - public static let type: String = "stream" - public let parent: String? // at least 1 char - public let url: Field>? - - static let parentValidator: AnyValueValidator = - makeStringValidator(minLength: 1) - - public convenience init(dictionary: [String: Any], templateToType: [TemplateName: String]) throws { - do { - self.init( - parent: try dictionary.getOptionalField("type", validator: Self.parentValidator), - url: try dictionary.getOptionalExpressionField("url", transform: URL.init(string:)) - ) - } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) { - throw DeserializationError.invalidFieldRepresentation(field: "div-video-data-stream_template." + field, representation: representation) - } - } - - init( - parent: String?, - url: Field>? = nil - ) { - self.parent = parent - self.url = url - } - - private static func resolveOnlyLinks(context: TemplatesContext, parent: DivVideoDataStreamTemplate?) -> DeserializationResult { - let urlValue = parent?.url?.resolveValue(context: context, transform: URL.init(string:)) ?? .noValue - var errors = mergeErrors( - urlValue.errorsOrWarnings?.map { .nestedObjectError(field: "url", error: $0) } - ) - if case .noValue = urlValue { - errors.append(.requiredFieldIsMissing(field: "url")) - } - guard - let urlNonNil = urlValue.value - else { - return .failure(NonEmptyArray(errors)!) - } - let result = DivVideoDataStream( - url: urlNonNil - ) - return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!) - } - - public static func resolveValue(context: TemplatesContext, parent: DivVideoDataStreamTemplate?, useOnlyLinks: Bool) -> DeserializationResult { - if useOnlyLinks { - return resolveOnlyLinks(context: context, parent: parent) - } - var urlValue: DeserializationResult> = parent?.url?.value() ?? .noValue - context.templateData.forEach { key, __dictValue in - switch key { - case "url": - urlValue = deserialize(__dictValue, transform: URL.init(string:)).merged(with: urlValue) - case parent?.url?.link: - urlValue = urlValue.merged(with: deserialize(__dictValue, transform: URL.init(string:))) - default: break - } - } - var errors = mergeErrors( - urlValue.errorsOrWarnings?.map { .nestedObjectError(field: "url", error: $0) } - ) - if case .noValue = urlValue { - errors.append(.requiredFieldIsMissing(field: "url")) - } - guard - let urlNonNil = urlValue.value - else { - return .failure(NonEmptyArray(errors)!) - } - let result = DivVideoDataStream( - url: urlNonNil - ) - return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!) - } - - private func mergedWithParent(templates: [TemplateName: Any]) throws -> DivVideoDataStreamTemplate { - guard let parent = parent, parent != Self.type else { return self } - guard let parentTemplate = templates[parent] as? DivVideoDataStreamTemplate else { - throw DeserializationError.unknownType(type: parent) - } - let mergedParent = try parentTemplate.mergedWithParent(templates: templates) - - return DivVideoDataStreamTemplate( - parent: nil, - url: url ?? mergedParent.url - ) - } - - public func resolveParent(templates: [TemplateName: Any]) throws -> DivVideoDataStreamTemplate { - return try mergedWithParent(templates: templates) - } -} diff --git a/DivKit/generated_sources/DivVideoDataTemplate.swift b/DivKit/generated_sources/DivVideoDataTemplate.swift deleted file mode 100644 index 40202ebe..00000000 --- a/DivKit/generated_sources/DivVideoDataTemplate.swift +++ /dev/null @@ -1,100 +0,0 @@ -// Generated code. Do not modify. - -import CommonCorePublic -import Foundation -import Serialization - -@frozen -public enum DivVideoDataTemplate: TemplateValue { - case divVideoDataVideoTemplate(DivVideoDataVideoTemplate) - case divVideoDataStreamTemplate(DivVideoDataStreamTemplate) - - public var value: Any { - switch self { - case let .divVideoDataVideoTemplate(value): - return value - case let .divVideoDataStreamTemplate(value): - return value - } - } - - public func resolveParent(templates: [TemplateName: Any]) throws -> DivVideoDataTemplate { - switch self { - case let .divVideoDataVideoTemplate(value): - return .divVideoDataVideoTemplate(try value.resolveParent(templates: templates)) - case let .divVideoDataStreamTemplate(value): - return .divVideoDataStreamTemplate(try value.resolveParent(templates: templates)) - } - } - - public static func resolveValue(context: TemplatesContext, parent: DivVideoDataTemplate?, useOnlyLinks: Bool) -> DeserializationResult { - guard let parent = parent else { - if useOnlyLinks { - return .failure(NonEmptyArray(.missingType(representation: context.templateData))) - } else { - return resolveUnknownValue(context: context, useOnlyLinks: useOnlyLinks) - } - } - - switch parent { - case let .divVideoDataVideoTemplate(value): - let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks) - switch result { - case let .success(value): return .success(.divVideoDataVideo(value)) - case let .partialSuccess(value, warnings): return .partialSuccess(.divVideoDataVideo(value), warnings: warnings) - case let .failure(errors): return .failure(errors) - case .noValue: return .noValue - } - case let .divVideoDataStreamTemplate(value): - let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks) - switch result { - case let .success(value): return .success(.divVideoDataStream(value)) - case let .partialSuccess(value, warnings): return .partialSuccess(.divVideoDataStream(value), warnings: warnings) - case let .failure(errors): return .failure(errors) - case .noValue: return .noValue - } - } - } - - private static func resolveUnknownValue(context: TemplatesContext, useOnlyLinks: Bool) -> DeserializationResult { - guard let type = (context.templateData["type"] as? String).flatMap({ context.templateToType[$0] ?? $0 }) else { - return .failure(NonEmptyArray(.requiredFieldIsMissing(field: "type"))) - } - - switch type { - case DivVideoDataVideo.type: - let result = DivVideoDataVideoTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks) - switch result { - case let .success(value): return .success(.divVideoDataVideo(value)) - case let .partialSuccess(value, warnings): return .partialSuccess(.divVideoDataVideo(value), warnings: warnings) - case let .failure(errors): return .failure(errors) - case .noValue: return .noValue - } - case DivVideoDataStream.type: - let result = DivVideoDataStreamTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks) - switch result { - case let .success(value): return .success(.divVideoDataStream(value)) - case let .partialSuccess(value, warnings): return .partialSuccess(.divVideoDataStream(value), warnings: warnings) - case let .failure(errors): return .failure(errors) - case .noValue: return .noValue - } - default: - return .failure(NonEmptyArray(.requiredFieldIsMissing(field: "type"))) - } - } -} - -extension DivVideoDataTemplate { - public init(dictionary: [String: Any], templateToType: [TemplateName: String]) throws { - let receivedType = try dictionary.getField("type") as String - let blockType = templateToType[receivedType] ?? receivedType - switch blockType { - case DivVideoDataVideoTemplate.type: - self = .divVideoDataVideoTemplate(try DivVideoDataVideoTemplate(dictionary: dictionary, templateToType: templateToType)) - case DivVideoDataStreamTemplate.type: - self = .divVideoDataStreamTemplate(try DivVideoDataStreamTemplate(dictionary: dictionary, templateToType: templateToType)) - default: - throw DeserializationError.invalidFieldRepresentation(field: "div-video-data_template", representation: dictionary) - } - } -} diff --git a/DivKit/generated_sources/DivVideoDataVideo.swift b/DivKit/generated_sources/DivVideoDataVideo.swift deleted file mode 100644 index dd054c7f..00000000 --- a/DivKit/generated_sources/DivVideoDataVideo.swift +++ /dev/null @@ -1,41 +0,0 @@ -// Generated code. Do not modify. - -import CommonCorePublic -import Foundation -import Serialization - -public final class DivVideoDataVideo { - public static let type: String = "video" - public let videoSources: [DivVideoDataVideoSource] // at least 1 elements - - static let videoSourcesValidator: AnyArrayValueValidator = - makeArrayValidator(minItems: 1) - - init( - videoSources: [DivVideoDataVideoSource] - ) { - self.videoSources = videoSources - } -} - -#if DEBUG -extension DivVideoDataVideo: Equatable { - public static func ==(lhs: DivVideoDataVideo, rhs: DivVideoDataVideo) -> Bool { - guard - lhs.videoSources == rhs.videoSources - else { - return false - } - return true - } -} -#endif - -extension DivVideoDataVideo: Serializable { - public func toDictionary() -> [String: ValidSerializationValue] { - var result: [String: ValidSerializationValue] = [:] - result["type"] = Self.type - result["video_sources"] = videoSources.map { $0.toDictionary() } - return result - } -} diff --git a/DivKit/generated_sources/DivVideoDataVideoTemplate.swift b/DivKit/generated_sources/DivVideoDataVideoTemplate.swift deleted file mode 100644 index c0bbc600..00000000 --- a/DivKit/generated_sources/DivVideoDataVideoTemplate.swift +++ /dev/null @@ -1,108 +0,0 @@ -// Generated code. Do not modify. - -import CommonCorePublic -import Foundation -import Serialization - -public final class DivVideoDataVideoTemplate: TemplateValue { - public static let type: String = "video" - public let parent: String? // at least 1 char - public let videoSources: Field<[DivVideoDataVideoSourceTemplate]>? // at least 1 elements - - static let parentValidator: AnyValueValidator = - makeStringValidator(minLength: 1) - - public convenience init(dictionary: [String: Any], templateToType: [TemplateName: String]) throws { - do { - self.init( - parent: try dictionary.getOptionalField("type", validator: Self.parentValidator), - videoSources: try dictionary.getOptionalArray("video_sources", templateToType: templateToType) - ) - } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) { - throw DeserializationError.invalidFieldRepresentation(field: "div-video-data-video_template." + field, representation: representation) - } - } - - init( - parent: String?, - videoSources: Field<[DivVideoDataVideoSourceTemplate]>? = nil - ) { - self.parent = parent - self.videoSources = videoSources - } - - private static func resolveOnlyLinks(context: TemplatesContext, parent: DivVideoDataVideoTemplate?) -> DeserializationResult { - let videoSourcesValue = parent?.videoSources?.resolveValue(context: context, validator: ResolvedValue.videoSourcesValidator, useOnlyLinks: true) ?? .noValue - var errors = mergeErrors( - videoSourcesValue.errorsOrWarnings?.map { .nestedObjectError(field: "video_sources", error: $0) } - ) - if case .noValue = videoSourcesValue { - errors.append(.requiredFieldIsMissing(field: "video_sources")) - } - guard - let videoSourcesNonNil = videoSourcesValue.value - else { - return .failure(NonEmptyArray(errors)!) - } - let result = DivVideoDataVideo( - videoSources: videoSourcesNonNil - ) - return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!) - } - - public static func resolveValue(context: TemplatesContext, parent: DivVideoDataVideoTemplate?, useOnlyLinks: Bool) -> DeserializationResult { - if useOnlyLinks { - return resolveOnlyLinks(context: context, parent: parent) - } - var videoSourcesValue: DeserializationResult<[DivVideoDataVideoSource]> = .noValue - context.templateData.forEach { key, __dictValue in - switch key { - case "video_sources": - videoSourcesValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.videoSourcesValidator, type: DivVideoDataVideoSourceTemplate.self).merged(with: videoSourcesValue) - case parent?.videoSources?.link: - videoSourcesValue = videoSourcesValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.videoSourcesValidator, type: DivVideoDataVideoSourceTemplate.self)) - default: break - } - } - if let parent = parent { - videoSourcesValue = videoSourcesValue.merged(with: parent.videoSources?.resolveValue(context: context, validator: ResolvedValue.videoSourcesValidator, useOnlyLinks: true)) - } - var errors = mergeErrors( - videoSourcesValue.errorsOrWarnings?.map { .nestedObjectError(field: "video_sources", error: $0) } - ) - if case .noValue = videoSourcesValue { - errors.append(.requiredFieldIsMissing(field: "video_sources")) - } - guard - let videoSourcesNonNil = videoSourcesValue.value - else { - return .failure(NonEmptyArray(errors)!) - } - let result = DivVideoDataVideo( - videoSources: videoSourcesNonNil - ) - return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!) - } - - private func mergedWithParent(templates: [TemplateName: Any]) throws -> DivVideoDataVideoTemplate { - guard let parent = parent, parent != Self.type else { return self } - guard let parentTemplate = templates[parent] as? DivVideoDataVideoTemplate else { - throw DeserializationError.unknownType(type: parent) - } - let mergedParent = try parentTemplate.mergedWithParent(templates: templates) - - return DivVideoDataVideoTemplate( - parent: nil, - videoSources: videoSources ?? mergedParent.videoSources - ) - } - - public func resolveParent(templates: [TemplateName: Any]) throws -> DivVideoDataVideoTemplate { - let merged = try mergedWithParent(templates: templates) - - return DivVideoDataVideoTemplate( - parent: nil, - videoSources: try merged.videoSources?.resolveParent(templates: templates) - ) - } -} diff --git a/DivKit/generated_sources/DivVideoDataVideoSource.swift b/DivKit/generated_sources/DivVideoSource.swift similarity index 67% rename from DivKit/generated_sources/DivVideoDataVideoSource.swift rename to DivKit/generated_sources/DivVideoSource.swift index 69afaf8c..8a688261 100644 --- a/DivKit/generated_sources/DivVideoDataVideoSource.swift +++ b/DivKit/generated_sources/DivVideoSource.swift @@ -4,7 +4,7 @@ import CommonCorePublic import Foundation import Serialization -public final class DivVideoDataVideoSource { +public final class DivVideoSource { public final class Resolution { public static let type: String = "resolution" public let height: Expression // constraint: number > 0 @@ -34,13 +34,13 @@ public final class DivVideoDataVideoSource { } public static let type: String = "video_source" - public let codec: Expression? - public let mimeType: Expression? + public let bitrate: Expression? + public let mimeType: Expression public let resolution: Resolution? public let url: Expression - public func resolveCodec(_ resolver: ExpressionResolver) -> String? { - resolver.resolveStringBasedValue(expression: codec, initializer: { $0 }) + public func resolveBitrate(_ resolver: ExpressionResolver) -> Int? { + resolver.resolveNumericValue(expression: bitrate) } public func resolveMimeType(_ resolver: ExpressionResolver) -> String? { @@ -51,22 +51,16 @@ public final class DivVideoDataVideoSource { resolver.resolveStringBasedValue(expression: url, initializer: URL.init(string:)) } - static let codecValidator: AnyValueValidator = - makeStringValidator(minLength: 1) - - static let mimeTypeValidator: AnyValueValidator = - makeStringValidator(minLength: 1) - - static let resolutionValidator: AnyValueValidator = + static let resolutionValidator: AnyValueValidator = makeNoOpValueValidator() init( - codec: Expression? = nil, - mimeType: Expression? = nil, + bitrate: Expression? = nil, + mimeType: Expression, resolution: Resolution? = nil, url: Expression ) { - self.codec = codec + self.bitrate = bitrate self.mimeType = mimeType self.resolution = resolution self.url = url @@ -74,10 +68,10 @@ public final class DivVideoDataVideoSource { } #if DEBUG -extension DivVideoDataVideoSource: Equatable { - public static func ==(lhs: DivVideoDataVideoSource, rhs: DivVideoDataVideoSource) -> Bool { +extension DivVideoSource: Equatable { + public static func ==(lhs: DivVideoSource, rhs: DivVideoSource) -> Bool { guard - lhs.codec == rhs.codec, + lhs.bitrate == rhs.bitrate, lhs.mimeType == rhs.mimeType, lhs.resolution == rhs.resolution else { @@ -93,12 +87,12 @@ extension DivVideoDataVideoSource: Equatable { } #endif -extension DivVideoDataVideoSource: Serializable { +extension DivVideoSource: Serializable { public func toDictionary() -> [String: ValidSerializationValue] { var result: [String: ValidSerializationValue] = [:] result["type"] = Self.type - result["codec"] = codec?.toValidSerializationValue() - result["mime_type"] = mimeType?.toValidSerializationValue() + result["bitrate"] = bitrate?.toValidSerializationValue() + result["mime_type"] = mimeType.toValidSerializationValue() result["resolution"] = resolution?.toDictionary() result["url"] = url.toValidSerializationValue() return result @@ -106,8 +100,8 @@ extension DivVideoDataVideoSource: Serializable { } #if DEBUG -extension DivVideoDataVideoSource.Resolution: Equatable { - public static func ==(lhs: DivVideoDataVideoSource.Resolution, rhs: DivVideoDataVideoSource.Resolution) -> Bool { +extension DivVideoSource.Resolution: Equatable { + public static func ==(lhs: DivVideoSource.Resolution, rhs: DivVideoSource.Resolution) -> Bool { guard lhs.height == rhs.height, lhs.width == rhs.width @@ -119,7 +113,7 @@ extension DivVideoDataVideoSource.Resolution: Equatable { } #endif -extension DivVideoDataVideoSource.Resolution: Serializable { +extension DivVideoSource.Resolution: Serializable { public func toDictionary() -> [String: ValidSerializationValue] { var result: [String: ValidSerializationValue] = [:] result["type"] = Self.type diff --git a/DivKit/generated_sources/DivVideoDataVideoSourceTemplate.swift b/DivKit/generated_sources/DivVideoSourceTemplate.swift similarity index 80% rename from DivKit/generated_sources/DivVideoDataVideoSourceTemplate.swift rename to DivKit/generated_sources/DivVideoSourceTemplate.swift index 71b0eb48..75592b2a 100644 --- a/DivKit/generated_sources/DivVideoDataVideoSourceTemplate.swift +++ b/DivKit/generated_sources/DivVideoSourceTemplate.swift @@ -4,7 +4,7 @@ import CommonCorePublic import Foundation import Serialization -public final class DivVideoDataVideoSourceTemplate: TemplateValue { +public final class DivVideoSourceTemplate: TemplateValue { public final class ResolutionTemplate: TemplateValue { public static let type: String = "resolution" public let parent: String? // at least 1 char @@ -36,7 +36,7 @@ public final class DivVideoDataVideoSourceTemplate: TemplateValue { self.width = width } - private static func resolveOnlyLinks(context: TemplatesContext, parent: ResolutionTemplate?) -> DeserializationResult { + private static func resolveOnlyLinks(context: TemplatesContext, parent: ResolutionTemplate?) -> DeserializationResult { let heightValue = parent?.height?.resolveValue(context: context, validator: ResolvedValue.heightValidator) ?? .noValue let widthValue = parent?.width?.resolveValue(context: context, validator: ResolvedValue.widthValidator) ?? .noValue var errors = mergeErrors( @@ -55,14 +55,14 @@ public final class DivVideoDataVideoSourceTemplate: TemplateValue { else { return .failure(NonEmptyArray(errors)!) } - let result = DivVideoDataVideoSource.Resolution( + let result = DivVideoSource.Resolution( height: heightNonNil, width: widthNonNil ) return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!) } - public static func resolveValue(context: TemplatesContext, parent: ResolutionTemplate?, useOnlyLinks: Bool) -> DeserializationResult { + public static func resolveValue(context: TemplatesContext, parent: ResolutionTemplate?, useOnlyLinks: Bool) -> DeserializationResult { if useOnlyLinks { return resolveOnlyLinks(context: context, parent: parent) } @@ -97,7 +97,7 @@ public final class DivVideoDataVideoSourceTemplate: TemplateValue { else { return .failure(NonEmptyArray(errors)!) } - let result = DivVideoDataVideoSource.Resolution( + let result = DivVideoSource.Resolution( height: heightNonNil, width: widthNonNil ) @@ -125,7 +125,7 @@ public final class DivVideoDataVideoSourceTemplate: TemplateValue { public static let type: String = "video_source" public let parent: String? // at least 1 char - public let codec: Field>? + public let bitrate: Field>? public let mimeType: Field>? public let resolution: Field? public let url: Field>? @@ -137,82 +137,86 @@ public final class DivVideoDataVideoSourceTemplate: TemplateValue { do { self.init( parent: try dictionary.getOptionalField("type", validator: Self.parentValidator), - codec: try dictionary.getOptionalExpressionField("codec"), + bitrate: try dictionary.getOptionalExpressionField("bitrate"), mimeType: try dictionary.getOptionalExpressionField("mime_type"), resolution: try dictionary.getOptionalField("resolution", templateToType: templateToType), url: try dictionary.getOptionalExpressionField("url", transform: URL.init(string:)) ) } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) { - throw DeserializationError.invalidFieldRepresentation(field: "div-video-data-video-source_template." + field, representation: representation) + throw DeserializationError.invalidFieldRepresentation(field: "div-video-source_template." + field, representation: representation) } } init( parent: String?, - codec: Field>? = nil, + bitrate: Field>? = nil, mimeType: Field>? = nil, resolution: Field? = nil, url: Field>? = nil ) { self.parent = parent - self.codec = codec + self.bitrate = bitrate self.mimeType = mimeType self.resolution = resolution self.url = url } - private static func resolveOnlyLinks(context: TemplatesContext, parent: DivVideoDataVideoSourceTemplate?) -> DeserializationResult { - let codecValue = parent?.codec?.resolveOptionalValue(context: context, validator: ResolvedValue.codecValidator) ?? .noValue - let mimeTypeValue = parent?.mimeType?.resolveOptionalValue(context: context, validator: ResolvedValue.mimeTypeValidator) ?? .noValue + private static func resolveOnlyLinks(context: TemplatesContext, parent: DivVideoSourceTemplate?) -> DeserializationResult { + let bitrateValue = parent?.bitrate?.resolveOptionalValue(context: context) ?? .noValue + let mimeTypeValue = parent?.mimeType?.resolveValue(context: context) ?? .noValue let resolutionValue = parent?.resolution?.resolveOptionalValue(context: context, validator: ResolvedValue.resolutionValidator, useOnlyLinks: true) ?? .noValue let urlValue = parent?.url?.resolveValue(context: context, transform: URL.init(string:)) ?? .noValue var errors = mergeErrors( - codecValue.errorsOrWarnings?.map { .nestedObjectError(field: "codec", error: $0) }, + bitrateValue.errorsOrWarnings?.map { .nestedObjectError(field: "bitrate", error: $0) }, mimeTypeValue.errorsOrWarnings?.map { .nestedObjectError(field: "mime_type", error: $0) }, resolutionValue.errorsOrWarnings?.map { .nestedObjectError(field: "resolution", error: $0) }, urlValue.errorsOrWarnings?.map { .nestedObjectError(field: "url", error: $0) } ) + if case .noValue = mimeTypeValue { + errors.append(.requiredFieldIsMissing(field: "mime_type")) + } if case .noValue = urlValue { errors.append(.requiredFieldIsMissing(field: "url")) } guard + let mimeTypeNonNil = mimeTypeValue.value, let urlNonNil = urlValue.value else { return .failure(NonEmptyArray(errors)!) } - let result = DivVideoDataVideoSource( - codec: codecValue.value, - mimeType: mimeTypeValue.value, + let result = DivVideoSource( + bitrate: bitrateValue.value, + mimeType: mimeTypeNonNil, resolution: resolutionValue.value, url: urlNonNil ) return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!) } - public static func resolveValue(context: TemplatesContext, parent: DivVideoDataVideoSourceTemplate?, useOnlyLinks: Bool) -> DeserializationResult { + public static func resolveValue(context: TemplatesContext, parent: DivVideoSourceTemplate?, useOnlyLinks: Bool) -> DeserializationResult { if useOnlyLinks { return resolveOnlyLinks(context: context, parent: parent) } - var codecValue: DeserializationResult> = parent?.codec?.value() ?? .noValue + var bitrateValue: DeserializationResult> = parent?.bitrate?.value() ?? .noValue var mimeTypeValue: DeserializationResult> = parent?.mimeType?.value() ?? .noValue - var resolutionValue: DeserializationResult = .noValue + var resolutionValue: DeserializationResult = .noValue var urlValue: DeserializationResult> = parent?.url?.value() ?? .noValue context.templateData.forEach { key, __dictValue in switch key { - case "codec": - codecValue = deserialize(__dictValue, validator: ResolvedValue.codecValidator).merged(with: codecValue) + case "bitrate": + bitrateValue = deserialize(__dictValue).merged(with: bitrateValue) case "mime_type": - mimeTypeValue = deserialize(__dictValue, validator: ResolvedValue.mimeTypeValidator).merged(with: mimeTypeValue) + mimeTypeValue = deserialize(__dictValue).merged(with: mimeTypeValue) case "resolution": - resolutionValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.resolutionValidator, type: DivVideoDataVideoSourceTemplate.ResolutionTemplate.self).merged(with: resolutionValue) + resolutionValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.resolutionValidator, type: DivVideoSourceTemplate.ResolutionTemplate.self).merged(with: resolutionValue) case "url": urlValue = deserialize(__dictValue, transform: URL.init(string:)).merged(with: urlValue) - case parent?.codec?.link: - codecValue = codecValue.merged(with: deserialize(__dictValue, validator: ResolvedValue.codecValidator)) + case parent?.bitrate?.link: + bitrateValue = bitrateValue.merged(with: deserialize(__dictValue)) case parent?.mimeType?.link: - mimeTypeValue = mimeTypeValue.merged(with: deserialize(__dictValue, validator: ResolvedValue.mimeTypeValidator)) + mimeTypeValue = mimeTypeValue.merged(with: deserialize(__dictValue)) case parent?.resolution?.link: - resolutionValue = resolutionValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.resolutionValidator, type: DivVideoDataVideoSourceTemplate.ResolutionTemplate.self)) + resolutionValue = resolutionValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.resolutionValidator, type: DivVideoSourceTemplate.ResolutionTemplate.self)) case parent?.url?.link: urlValue = urlValue.merged(with: deserialize(__dictValue, transform: URL.init(string:))) default: break @@ -222,50 +226,54 @@ public final class DivVideoDataVideoSourceTemplate: TemplateValue { resolutionValue = resolutionValue.merged(with: parent.resolution?.resolveOptionalValue(context: context, validator: ResolvedValue.resolutionValidator, useOnlyLinks: true)) } var errors = mergeErrors( - codecValue.errorsOrWarnings?.map { .nestedObjectError(field: "codec", error: $0) }, + bitrateValue.errorsOrWarnings?.map { .nestedObjectError(field: "bitrate", error: $0) }, mimeTypeValue.errorsOrWarnings?.map { .nestedObjectError(field: "mime_type", error: $0) }, resolutionValue.errorsOrWarnings?.map { .nestedObjectError(field: "resolution", error: $0) }, urlValue.errorsOrWarnings?.map { .nestedObjectError(field: "url", error: $0) } ) + if case .noValue = mimeTypeValue { + errors.append(.requiredFieldIsMissing(field: "mime_type")) + } if case .noValue = urlValue { errors.append(.requiredFieldIsMissing(field: "url")) } guard + let mimeTypeNonNil = mimeTypeValue.value, let urlNonNil = urlValue.value else { return .failure(NonEmptyArray(errors)!) } - let result = DivVideoDataVideoSource( - codec: codecValue.value, - mimeType: mimeTypeValue.value, + let result = DivVideoSource( + bitrate: bitrateValue.value, + mimeType: mimeTypeNonNil, resolution: resolutionValue.value, url: urlNonNil ) return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!) } - private func mergedWithParent(templates: [TemplateName: Any]) throws -> DivVideoDataVideoSourceTemplate { + private func mergedWithParent(templates: [TemplateName: Any]) throws -> DivVideoSourceTemplate { guard let parent = parent, parent != Self.type else { return self } - guard let parentTemplate = templates[parent] as? DivVideoDataVideoSourceTemplate else { + guard let parentTemplate = templates[parent] as? DivVideoSourceTemplate else { throw DeserializationError.unknownType(type: parent) } let mergedParent = try parentTemplate.mergedWithParent(templates: templates) - return DivVideoDataVideoSourceTemplate( + return DivVideoSourceTemplate( parent: nil, - codec: codec ?? mergedParent.codec, + bitrate: bitrate ?? mergedParent.bitrate, mimeType: mimeType ?? mergedParent.mimeType, resolution: resolution ?? mergedParent.resolution, url: url ?? mergedParent.url ) } - public func resolveParent(templates: [TemplateName: Any]) throws -> DivVideoDataVideoSourceTemplate { + public func resolveParent(templates: [TemplateName: Any]) throws -> DivVideoSourceTemplate { let merged = try mergedWithParent(templates: templates) - return DivVideoDataVideoSourceTemplate( + return DivVideoSourceTemplate( parent: nil, - codec: merged.codec, + bitrate: merged.bitrate, mimeType: merged.mimeType, resolution: merged.resolution?.tryResolveParent(templates: templates), url: merged.url diff --git a/DivKit/generated_sources/DivVideoTemplate.swift b/DivKit/generated_sources/DivVideoTemplate.swift index 9da5abe7..5339411b 100644 --- a/DivKit/generated_sources/DivVideoTemplate.swift +++ b/DivKit/generated_sources/DivVideoTemplate.swift @@ -19,12 +19,14 @@ public final class DivVideoTemplate: TemplateValue { public let elapsedTimeVariable: Field? // at least 1 char public let endActions: Field<[DivActionTemplate]>? // at least 1 elements public let extensions: Field<[DivExtensionTemplate]>? // at least 1 elements + public let fatalActions: Field<[DivActionTemplate]>? // at least 1 elements public let focus: Field? public let height: Field? // default value: .divWrapContentSize(DivWrapContentSize()) public let id: Field? // at least 1 char public let margins: Field? public let muted: Field>? // default value: false public let paddings: Field? + public let pauseActions: Field<[DivActionTemplate]>? // at least 1 elements public let playerSettingsPayload: Field<[String: Any]>? public let preview: Field>? // at least 1 char public let repeatable: Field>? // default value: false @@ -37,7 +39,7 @@ public final class DivVideoTemplate: TemplateValue { public let transitionIn: Field? public let transitionOut: Field? public let transitionTriggers: Field<[DivTransitionTrigger]>? // at least 1 elements - public let videoData: Field? + public let videoSources: Field<[DivVideoSourceTemplate]>? // at least 1 elements public let visibility: Field>? // default value: visible public let visibilityAction: Field? public let visibilityActions: Field<[DivVisibilityActionTemplate]>? // at least 1 elements @@ -62,12 +64,14 @@ public final class DivVideoTemplate: TemplateValue { elapsedTimeVariable: try dictionary.getOptionalField("elapsed_time_variable"), endActions: try dictionary.getOptionalArray("end_actions", templateToType: templateToType), extensions: try dictionary.getOptionalArray("extensions", templateToType: templateToType), + fatalActions: try dictionary.getOptionalArray("fatal_actions", templateToType: templateToType), focus: try dictionary.getOptionalField("focus", templateToType: templateToType), height: try dictionary.getOptionalField("height", templateToType: templateToType), id: try dictionary.getOptionalField("id"), margins: try dictionary.getOptionalField("margins", templateToType: templateToType), muted: try dictionary.getOptionalExpressionField("muted"), paddings: try dictionary.getOptionalField("paddings", templateToType: templateToType), + pauseActions: try dictionary.getOptionalArray("pause_actions", templateToType: templateToType), playerSettingsPayload: try dictionary.getOptionalField("player_settings_payload"), preview: try dictionary.getOptionalExpressionField("preview"), repeatable: try dictionary.getOptionalExpressionField("repeatable"), @@ -80,7 +84,7 @@ public final class DivVideoTemplate: TemplateValue { transitionIn: try dictionary.getOptionalField("transition_in", templateToType: templateToType), transitionOut: try dictionary.getOptionalField("transition_out", templateToType: templateToType), transitionTriggers: try dictionary.getOptionalArray("transition_triggers"), - videoData: try dictionary.getOptionalField("video_data", templateToType: templateToType), + videoSources: try dictionary.getOptionalArray("video_sources", templateToType: templateToType), visibility: try dictionary.getOptionalExpressionField("visibility"), visibilityAction: try dictionary.getOptionalField("visibility_action", templateToType: templateToType), visibilityActions: try dictionary.getOptionalArray("visibility_actions", templateToType: templateToType), @@ -105,12 +109,14 @@ public final class DivVideoTemplate: TemplateValue { elapsedTimeVariable: Field? = nil, endActions: Field<[DivActionTemplate]>? = nil, extensions: Field<[DivExtensionTemplate]>? = nil, + fatalActions: Field<[DivActionTemplate]>? = nil, focus: Field? = nil, height: Field? = nil, id: Field? = nil, margins: Field? = nil, muted: Field>? = nil, paddings: Field? = nil, + pauseActions: Field<[DivActionTemplate]>? = nil, playerSettingsPayload: Field<[String: Any]>? = nil, preview: Field>? = nil, repeatable: Field>? = nil, @@ -123,7 +129,7 @@ public final class DivVideoTemplate: TemplateValue { transitionIn: Field? = nil, transitionOut: Field? = nil, transitionTriggers: Field<[DivTransitionTrigger]>? = nil, - videoData: Field? = nil, + videoSources: Field<[DivVideoSourceTemplate]>? = nil, visibility: Field>? = nil, visibilityAction: Field? = nil, visibilityActions: Field<[DivVisibilityActionTemplate]>? = nil, @@ -142,12 +148,14 @@ public final class DivVideoTemplate: TemplateValue { self.elapsedTimeVariable = elapsedTimeVariable self.endActions = endActions self.extensions = extensions + self.fatalActions = fatalActions self.focus = focus self.height = height self.id = id self.margins = margins self.muted = muted self.paddings = paddings + self.pauseActions = pauseActions self.playerSettingsPayload = playerSettingsPayload self.preview = preview self.repeatable = repeatable @@ -160,7 +168,7 @@ public final class DivVideoTemplate: TemplateValue { self.transitionIn = transitionIn self.transitionOut = transitionOut self.transitionTriggers = transitionTriggers - self.videoData = videoData + self.videoSources = videoSources self.visibility = visibility self.visibilityAction = visibilityAction self.visibilityActions = visibilityActions @@ -180,12 +188,14 @@ public final class DivVideoTemplate: TemplateValue { let elapsedTimeVariableValue = parent?.elapsedTimeVariable?.resolveOptionalValue(context: context, validator: ResolvedValue.elapsedTimeVariableValidator) ?? .noValue let endActionsValue = parent?.endActions?.resolveOptionalValue(context: context, validator: ResolvedValue.endActionsValidator, useOnlyLinks: true) ?? .noValue let extensionsValue = parent?.extensions?.resolveOptionalValue(context: context, validator: ResolvedValue.extensionsValidator, useOnlyLinks: true) ?? .noValue + let fatalActionsValue = parent?.fatalActions?.resolveOptionalValue(context: context, validator: ResolvedValue.fatalActionsValidator, useOnlyLinks: true) ?? .noValue let focusValue = parent?.focus?.resolveOptionalValue(context: context, validator: ResolvedValue.focusValidator, useOnlyLinks: true) ?? .noValue let heightValue = parent?.height?.resolveOptionalValue(context: context, validator: ResolvedValue.heightValidator, useOnlyLinks: true) ?? .noValue let idValue = parent?.id?.resolveOptionalValue(context: context, validator: ResolvedValue.idValidator) ?? .noValue let marginsValue = parent?.margins?.resolveOptionalValue(context: context, validator: ResolvedValue.marginsValidator, useOnlyLinks: true) ?? .noValue let mutedValue = parent?.muted?.resolveOptionalValue(context: context, validator: ResolvedValue.mutedValidator) ?? .noValue let paddingsValue = parent?.paddings?.resolveOptionalValue(context: context, validator: ResolvedValue.paddingsValidator, useOnlyLinks: true) ?? .noValue + let pauseActionsValue = parent?.pauseActions?.resolveOptionalValue(context: context, validator: ResolvedValue.pauseActionsValidator, useOnlyLinks: true) ?? .noValue let playerSettingsPayloadValue = parent?.playerSettingsPayload?.resolveOptionalValue(context: context, validator: ResolvedValue.playerSettingsPayloadValidator) ?? .noValue let previewValue = parent?.preview?.resolveOptionalValue(context: context, validator: ResolvedValue.previewValidator) ?? .noValue let repeatableValue = parent?.repeatable?.resolveOptionalValue(context: context, validator: ResolvedValue.repeatableValidator) ?? .noValue @@ -198,7 +208,7 @@ public final class DivVideoTemplate: TemplateValue { let transitionInValue = parent?.transitionIn?.resolveOptionalValue(context: context, validator: ResolvedValue.transitionInValidator, useOnlyLinks: true) ?? .noValue let transitionOutValue = parent?.transitionOut?.resolveOptionalValue(context: context, validator: ResolvedValue.transitionOutValidator, useOnlyLinks: true) ?? .noValue let transitionTriggersValue = parent?.transitionTriggers?.resolveOptionalValue(context: context, validator: ResolvedValue.transitionTriggersValidator) ?? .noValue - let videoDataValue = parent?.videoData?.resolveValue(context: context, useOnlyLinks: true) ?? .noValue + let videoSourcesValue = parent?.videoSources?.resolveValue(context: context, validator: ResolvedValue.videoSourcesValidator, useOnlyLinks: true) ?? .noValue let visibilityValue = parent?.visibility?.resolveOptionalValue(context: context, validator: ResolvedValue.visibilityValidator) ?? .noValue let visibilityActionValue = parent?.visibilityAction?.resolveOptionalValue(context: context, validator: ResolvedValue.visibilityActionValidator, useOnlyLinks: true) ?? .noValue let visibilityActionsValue = parent?.visibilityActions?.resolveOptionalValue(context: context, validator: ResolvedValue.visibilityActionsValidator, useOnlyLinks: true) ?? .noValue @@ -216,12 +226,14 @@ public final class DivVideoTemplate: TemplateValue { elapsedTimeVariableValue.errorsOrWarnings?.map { .nestedObjectError(field: "elapsed_time_variable", error: $0) }, endActionsValue.errorsOrWarnings?.map { .nestedObjectError(field: "end_actions", error: $0) }, extensionsValue.errorsOrWarnings?.map { .nestedObjectError(field: "extensions", error: $0) }, + fatalActionsValue.errorsOrWarnings?.map { .nestedObjectError(field: "fatal_actions", error: $0) }, focusValue.errorsOrWarnings?.map { .nestedObjectError(field: "focus", error: $0) }, heightValue.errorsOrWarnings?.map { .nestedObjectError(field: "height", error: $0) }, idValue.errorsOrWarnings?.map { .nestedObjectError(field: "id", error: $0) }, marginsValue.errorsOrWarnings?.map { .nestedObjectError(field: "margins", error: $0) }, mutedValue.errorsOrWarnings?.map { .nestedObjectError(field: "muted", error: $0) }, paddingsValue.errorsOrWarnings?.map { .nestedObjectError(field: "paddings", error: $0) }, + pauseActionsValue.errorsOrWarnings?.map { .nestedObjectError(field: "pause_actions", error: $0) }, playerSettingsPayloadValue.errorsOrWarnings?.map { .nestedObjectError(field: "player_settings_payload", error: $0) }, previewValue.errorsOrWarnings?.map { .nestedObjectError(field: "preview", error: $0) }, repeatableValue.errorsOrWarnings?.map { .nestedObjectError(field: "repeatable", error: $0) }, @@ -234,17 +246,17 @@ public final class DivVideoTemplate: TemplateValue { transitionInValue.errorsOrWarnings?.map { .nestedObjectError(field: "transition_in", error: $0) }, transitionOutValue.errorsOrWarnings?.map { .nestedObjectError(field: "transition_out", error: $0) }, transitionTriggersValue.errorsOrWarnings?.map { .nestedObjectError(field: "transition_triggers", error: $0) }, - videoDataValue.errorsOrWarnings?.map { .nestedObjectError(field: "video_data", error: $0) }, + videoSourcesValue.errorsOrWarnings?.map { .nestedObjectError(field: "video_sources", error: $0) }, visibilityValue.errorsOrWarnings?.map { .nestedObjectError(field: "visibility", error: $0) }, visibilityActionValue.errorsOrWarnings?.map { .nestedObjectError(field: "visibility_action", error: $0) }, visibilityActionsValue.errorsOrWarnings?.map { .nestedObjectError(field: "visibility_actions", error: $0) }, widthValue.errorsOrWarnings?.map { .nestedObjectError(field: "width", error: $0) } ) - if case .noValue = videoDataValue { - errors.append(.requiredFieldIsMissing(field: "video_data")) + if case .noValue = videoSourcesValue { + errors.append(.requiredFieldIsMissing(field: "video_sources")) } guard - let videoDataNonNil = videoDataValue.value + let videoSourcesNonNil = videoSourcesValue.value else { return .failure(NonEmptyArray(errors)!) } @@ -261,12 +273,14 @@ public final class DivVideoTemplate: TemplateValue { elapsedTimeVariable: elapsedTimeVariableValue.value, endActions: endActionsValue.value, extensions: extensionsValue.value, + fatalActions: fatalActionsValue.value, focus: focusValue.value, height: heightValue.value, id: idValue.value, margins: marginsValue.value, muted: mutedValue.value, paddings: paddingsValue.value, + pauseActions: pauseActionsValue.value, playerSettingsPayload: playerSettingsPayloadValue.value, preview: previewValue.value, repeatable: repeatableValue.value, @@ -279,7 +293,7 @@ public final class DivVideoTemplate: TemplateValue { transitionIn: transitionInValue.value, transitionOut: transitionOutValue.value, transitionTriggers: transitionTriggersValue.value, - videoData: videoDataNonNil, + videoSources: videoSourcesNonNil, visibility: visibilityValue.value, visibilityAction: visibilityActionValue.value, visibilityActions: visibilityActionsValue.value, @@ -304,12 +318,14 @@ public final class DivVideoTemplate: TemplateValue { var elapsedTimeVariableValue: DeserializationResult = parent?.elapsedTimeVariable?.value(validatedBy: ResolvedValue.elapsedTimeVariableValidator) ?? .noValue var endActionsValue: DeserializationResult<[DivAction]> = .noValue var extensionsValue: DeserializationResult<[DivExtension]> = .noValue + var fatalActionsValue: DeserializationResult<[DivAction]> = .noValue var focusValue: DeserializationResult = .noValue var heightValue: DeserializationResult = .noValue var idValue: DeserializationResult = parent?.id?.value(validatedBy: ResolvedValue.idValidator) ?? .noValue var marginsValue: DeserializationResult = .noValue var mutedValue: DeserializationResult> = parent?.muted?.value() ?? .noValue var paddingsValue: DeserializationResult = .noValue + var pauseActionsValue: DeserializationResult<[DivAction]> = .noValue var playerSettingsPayloadValue: DeserializationResult<[String: Any]> = parent?.playerSettingsPayload?.value(validatedBy: ResolvedValue.playerSettingsPayloadValidator) ?? .noValue var previewValue: DeserializationResult> = parent?.preview?.value() ?? .noValue var repeatableValue: DeserializationResult> = parent?.repeatable?.value() ?? .noValue @@ -322,7 +338,7 @@ public final class DivVideoTemplate: TemplateValue { var transitionInValue: DeserializationResult = .noValue var transitionOutValue: DeserializationResult = .noValue var transitionTriggersValue: DeserializationResult<[DivTransitionTrigger]> = parent?.transitionTriggers?.value(validatedBy: ResolvedValue.transitionTriggersValidator) ?? .noValue - var videoDataValue: DeserializationResult = .noValue + var videoSourcesValue: DeserializationResult<[DivVideoSource]> = .noValue var visibilityValue: DeserializationResult> = parent?.visibility?.value() ?? .noValue var visibilityActionValue: DeserializationResult = .noValue var visibilityActionsValue: DeserializationResult<[DivVisibilityAction]> = .noValue @@ -353,6 +369,8 @@ public final class DivVideoTemplate: TemplateValue { endActionsValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.endActionsValidator, type: DivActionTemplate.self).merged(with: endActionsValue) case "extensions": extensionsValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.extensionsValidator, type: DivExtensionTemplate.self).merged(with: extensionsValue) + case "fatal_actions": + fatalActionsValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.fatalActionsValidator, type: DivActionTemplate.self).merged(with: fatalActionsValue) case "focus": focusValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.focusValidator, type: DivFocusTemplate.self).merged(with: focusValue) case "height": @@ -365,6 +383,8 @@ public final class DivVideoTemplate: TemplateValue { mutedValue = deserialize(__dictValue, validator: ResolvedValue.mutedValidator).merged(with: mutedValue) case "paddings": paddingsValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.paddingsValidator, type: DivEdgeInsetsTemplate.self).merged(with: paddingsValue) + case "pause_actions": + pauseActionsValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.pauseActionsValidator, type: DivActionTemplate.self).merged(with: pauseActionsValue) case "player_settings_payload": playerSettingsPayloadValue = deserialize(__dictValue, validator: ResolvedValue.playerSettingsPayloadValidator).merged(with: playerSettingsPayloadValue) case "preview": @@ -389,8 +409,8 @@ public final class DivVideoTemplate: TemplateValue { transitionOutValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.transitionOutValidator, type: DivAppearanceTransitionTemplate.self).merged(with: transitionOutValue) case "transition_triggers": transitionTriggersValue = deserialize(__dictValue, validator: ResolvedValue.transitionTriggersValidator).merged(with: transitionTriggersValue) - case "video_data": - videoDataValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: DivVideoDataTemplate.self).merged(with: videoDataValue) + case "video_sources": + videoSourcesValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.videoSourcesValidator, type: DivVideoSourceTemplate.self).merged(with: videoSourcesValue) case "visibility": visibilityValue = deserialize(__dictValue, validator: ResolvedValue.visibilityValidator).merged(with: visibilityValue) case "visibility_action": @@ -423,6 +443,8 @@ public final class DivVideoTemplate: TemplateValue { endActionsValue = endActionsValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.endActionsValidator, type: DivActionTemplate.self)) case parent?.extensions?.link: extensionsValue = extensionsValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.extensionsValidator, type: DivExtensionTemplate.self)) + case parent?.fatalActions?.link: + fatalActionsValue = fatalActionsValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.fatalActionsValidator, type: DivActionTemplate.self)) case parent?.focus?.link: focusValue = focusValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.focusValidator, type: DivFocusTemplate.self)) case parent?.height?.link: @@ -435,6 +457,8 @@ public final class DivVideoTemplate: TemplateValue { mutedValue = mutedValue.merged(with: deserialize(__dictValue, validator: ResolvedValue.mutedValidator)) case parent?.paddings?.link: paddingsValue = paddingsValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.paddingsValidator, type: DivEdgeInsetsTemplate.self)) + case parent?.pauseActions?.link: + pauseActionsValue = pauseActionsValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.pauseActionsValidator, type: DivActionTemplate.self)) case parent?.playerSettingsPayload?.link: playerSettingsPayloadValue = playerSettingsPayloadValue.merged(with: deserialize(__dictValue, validator: ResolvedValue.playerSettingsPayloadValidator)) case parent?.preview?.link: @@ -459,8 +483,8 @@ public final class DivVideoTemplate: TemplateValue { transitionOutValue = transitionOutValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.transitionOutValidator, type: DivAppearanceTransitionTemplate.self)) case parent?.transitionTriggers?.link: transitionTriggersValue = transitionTriggersValue.merged(with: deserialize(__dictValue, validator: ResolvedValue.transitionTriggersValidator)) - case parent?.videoData?.link: - videoDataValue = videoDataValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: DivVideoDataTemplate.self)) + case parent?.videoSources?.link: + videoSourcesValue = videoSourcesValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.videoSourcesValidator, type: DivVideoSourceTemplate.self)) case parent?.visibility?.link: visibilityValue = visibilityValue.merged(with: deserialize(__dictValue, validator: ResolvedValue.visibilityValidator)) case parent?.visibilityAction?.link: @@ -479,10 +503,12 @@ public final class DivVideoTemplate: TemplateValue { bufferingActionsValue = bufferingActionsValue.merged(with: parent.bufferingActions?.resolveOptionalValue(context: context, validator: ResolvedValue.bufferingActionsValidator, useOnlyLinks: true)) endActionsValue = endActionsValue.merged(with: parent.endActions?.resolveOptionalValue(context: context, validator: ResolvedValue.endActionsValidator, useOnlyLinks: true)) extensionsValue = extensionsValue.merged(with: parent.extensions?.resolveOptionalValue(context: context, validator: ResolvedValue.extensionsValidator, useOnlyLinks: true)) + fatalActionsValue = fatalActionsValue.merged(with: parent.fatalActions?.resolveOptionalValue(context: context, validator: ResolvedValue.fatalActionsValidator, useOnlyLinks: true)) focusValue = focusValue.merged(with: parent.focus?.resolveOptionalValue(context: context, validator: ResolvedValue.focusValidator, useOnlyLinks: true)) heightValue = heightValue.merged(with: parent.height?.resolveOptionalValue(context: context, validator: ResolvedValue.heightValidator, useOnlyLinks: true)) marginsValue = marginsValue.merged(with: parent.margins?.resolveOptionalValue(context: context, validator: ResolvedValue.marginsValidator, useOnlyLinks: true)) paddingsValue = paddingsValue.merged(with: parent.paddings?.resolveOptionalValue(context: context, validator: ResolvedValue.paddingsValidator, useOnlyLinks: true)) + pauseActionsValue = pauseActionsValue.merged(with: parent.pauseActions?.resolveOptionalValue(context: context, validator: ResolvedValue.pauseActionsValidator, useOnlyLinks: true)) resumeActionsValue = resumeActionsValue.merged(with: parent.resumeActions?.resolveOptionalValue(context: context, validator: ResolvedValue.resumeActionsValidator, useOnlyLinks: true)) selectedActionsValue = selectedActionsValue.merged(with: parent.selectedActions?.resolveOptionalValue(context: context, validator: ResolvedValue.selectedActionsValidator, useOnlyLinks: true)) tooltipsValue = tooltipsValue.merged(with: parent.tooltips?.resolveOptionalValue(context: context, validator: ResolvedValue.tooltipsValidator, useOnlyLinks: true)) @@ -490,7 +516,7 @@ public final class DivVideoTemplate: TemplateValue { transitionChangeValue = transitionChangeValue.merged(with: parent.transitionChange?.resolveOptionalValue(context: context, validator: ResolvedValue.transitionChangeValidator, useOnlyLinks: true)) transitionInValue = transitionInValue.merged(with: parent.transitionIn?.resolveOptionalValue(context: context, validator: ResolvedValue.transitionInValidator, useOnlyLinks: true)) transitionOutValue = transitionOutValue.merged(with: parent.transitionOut?.resolveOptionalValue(context: context, validator: ResolvedValue.transitionOutValidator, useOnlyLinks: true)) - videoDataValue = videoDataValue.merged(with: parent.videoData?.resolveValue(context: context, useOnlyLinks: true)) + videoSourcesValue = videoSourcesValue.merged(with: parent.videoSources?.resolveValue(context: context, validator: ResolvedValue.videoSourcesValidator, useOnlyLinks: true)) visibilityActionValue = visibilityActionValue.merged(with: parent.visibilityAction?.resolveOptionalValue(context: context, validator: ResolvedValue.visibilityActionValidator, useOnlyLinks: true)) visibilityActionsValue = visibilityActionsValue.merged(with: parent.visibilityActions?.resolveOptionalValue(context: context, validator: ResolvedValue.visibilityActionsValidator, useOnlyLinks: true)) widthValue = widthValue.merged(with: parent.width?.resolveOptionalValue(context: context, validator: ResolvedValue.widthValidator, useOnlyLinks: true)) @@ -508,12 +534,14 @@ public final class DivVideoTemplate: TemplateValue { elapsedTimeVariableValue.errorsOrWarnings?.map { .nestedObjectError(field: "elapsed_time_variable", error: $0) }, endActionsValue.errorsOrWarnings?.map { .nestedObjectError(field: "end_actions", error: $0) }, extensionsValue.errorsOrWarnings?.map { .nestedObjectError(field: "extensions", error: $0) }, + fatalActionsValue.errorsOrWarnings?.map { .nestedObjectError(field: "fatal_actions", error: $0) }, focusValue.errorsOrWarnings?.map { .nestedObjectError(field: "focus", error: $0) }, heightValue.errorsOrWarnings?.map { .nestedObjectError(field: "height", error: $0) }, idValue.errorsOrWarnings?.map { .nestedObjectError(field: "id", error: $0) }, marginsValue.errorsOrWarnings?.map { .nestedObjectError(field: "margins", error: $0) }, mutedValue.errorsOrWarnings?.map { .nestedObjectError(field: "muted", error: $0) }, paddingsValue.errorsOrWarnings?.map { .nestedObjectError(field: "paddings", error: $0) }, + pauseActionsValue.errorsOrWarnings?.map { .nestedObjectError(field: "pause_actions", error: $0) }, playerSettingsPayloadValue.errorsOrWarnings?.map { .nestedObjectError(field: "player_settings_payload", error: $0) }, previewValue.errorsOrWarnings?.map { .nestedObjectError(field: "preview", error: $0) }, repeatableValue.errorsOrWarnings?.map { .nestedObjectError(field: "repeatable", error: $0) }, @@ -526,17 +554,17 @@ public final class DivVideoTemplate: TemplateValue { transitionInValue.errorsOrWarnings?.map { .nestedObjectError(field: "transition_in", error: $0) }, transitionOutValue.errorsOrWarnings?.map { .nestedObjectError(field: "transition_out", error: $0) }, transitionTriggersValue.errorsOrWarnings?.map { .nestedObjectError(field: "transition_triggers", error: $0) }, - videoDataValue.errorsOrWarnings?.map { .nestedObjectError(field: "video_data", error: $0) }, + videoSourcesValue.errorsOrWarnings?.map { .nestedObjectError(field: "video_sources", error: $0) }, visibilityValue.errorsOrWarnings?.map { .nestedObjectError(field: "visibility", error: $0) }, visibilityActionValue.errorsOrWarnings?.map { .nestedObjectError(field: "visibility_action", error: $0) }, visibilityActionsValue.errorsOrWarnings?.map { .nestedObjectError(field: "visibility_actions", error: $0) }, widthValue.errorsOrWarnings?.map { .nestedObjectError(field: "width", error: $0) } ) - if case .noValue = videoDataValue { - errors.append(.requiredFieldIsMissing(field: "video_data")) + if case .noValue = videoSourcesValue { + errors.append(.requiredFieldIsMissing(field: "video_sources")) } guard - let videoDataNonNil = videoDataValue.value + let videoSourcesNonNil = videoSourcesValue.value else { return .failure(NonEmptyArray(errors)!) } @@ -553,12 +581,14 @@ public final class DivVideoTemplate: TemplateValue { elapsedTimeVariable: elapsedTimeVariableValue.value, endActions: endActionsValue.value, extensions: extensionsValue.value, + fatalActions: fatalActionsValue.value, focus: focusValue.value, height: heightValue.value, id: idValue.value, margins: marginsValue.value, muted: mutedValue.value, paddings: paddingsValue.value, + pauseActions: pauseActionsValue.value, playerSettingsPayload: playerSettingsPayloadValue.value, preview: previewValue.value, repeatable: repeatableValue.value, @@ -571,7 +601,7 @@ public final class DivVideoTemplate: TemplateValue { transitionIn: transitionInValue.value, transitionOut: transitionOutValue.value, transitionTriggers: transitionTriggersValue.value, - videoData: videoDataNonNil, + videoSources: videoSourcesNonNil, visibility: visibilityValue.value, visibilityAction: visibilityActionValue.value, visibilityActions: visibilityActionsValue.value, @@ -601,12 +631,14 @@ public final class DivVideoTemplate: TemplateValue { elapsedTimeVariable: elapsedTimeVariable ?? mergedParent.elapsedTimeVariable, endActions: endActions ?? mergedParent.endActions, extensions: extensions ?? mergedParent.extensions, + fatalActions: fatalActions ?? mergedParent.fatalActions, focus: focus ?? mergedParent.focus, height: height ?? mergedParent.height, id: id ?? mergedParent.id, margins: margins ?? mergedParent.margins, muted: muted ?? mergedParent.muted, paddings: paddings ?? mergedParent.paddings, + pauseActions: pauseActions ?? mergedParent.pauseActions, playerSettingsPayload: playerSettingsPayload ?? mergedParent.playerSettingsPayload, preview: preview ?? mergedParent.preview, repeatable: repeatable ?? mergedParent.repeatable, @@ -619,7 +651,7 @@ public final class DivVideoTemplate: TemplateValue { transitionIn: transitionIn ?? mergedParent.transitionIn, transitionOut: transitionOut ?? mergedParent.transitionOut, transitionTriggers: transitionTriggers ?? mergedParent.transitionTriggers, - videoData: videoData ?? mergedParent.videoData, + videoSources: videoSources ?? mergedParent.videoSources, visibility: visibility ?? mergedParent.visibility, visibilityAction: visibilityAction ?? mergedParent.visibilityAction, visibilityActions: visibilityActions ?? mergedParent.visibilityActions, @@ -644,12 +676,14 @@ public final class DivVideoTemplate: TemplateValue { elapsedTimeVariable: merged.elapsedTimeVariable, endActions: merged.endActions?.tryResolveParent(templates: templates), extensions: merged.extensions?.tryResolveParent(templates: templates), + fatalActions: merged.fatalActions?.tryResolveParent(templates: templates), focus: merged.focus?.tryResolveParent(templates: templates), height: merged.height?.tryResolveParent(templates: templates), id: merged.id, margins: merged.margins?.tryResolveParent(templates: templates), muted: merged.muted, paddings: merged.paddings?.tryResolveParent(templates: templates), + pauseActions: merged.pauseActions?.tryResolveParent(templates: templates), playerSettingsPayload: merged.playerSettingsPayload, preview: merged.preview, repeatable: merged.repeatable, @@ -662,7 +696,7 @@ public final class DivVideoTemplate: TemplateValue { transitionIn: merged.transitionIn?.tryResolveParent(templates: templates), transitionOut: merged.transitionOut?.tryResolveParent(templates: templates), transitionTriggers: merged.transitionTriggers, - videoData: try merged.videoData?.resolveParent(templates: templates), + videoSources: try merged.videoSources?.resolveParent(templates: templates), visibility: merged.visibility, visibilityAction: merged.visibilityAction?.tryResolveParent(templates: templates), visibilityActions: merged.visibilityActions?.tryResolveParent(templates: templates), diff --git a/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/CorePlayer.swift b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/CorePlayer.swift new file mode 100644 index 00000000..cc51bdd3 --- /dev/null +++ b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/CorePlayer.swift @@ -0,0 +1,28 @@ +import BasePublic +import Foundation +import CoreMedia + +protocol CorePlayer: VideoEngineProvider { + static func isMIMETypeSupported(_ mimeType: String) -> Bool + + var playerStatusDidChange: Signal { get } + var playbackStatusDidChange: Signal { get } + var playbackDidFail: Signal { get } + var playbackDidFinish: Signal { get } + + func periodicCurrentTimeSignal(interval: TimeInterval) -> Signal + + func set(source: Video) + + func play() + func pause() + func seek(to position: CMTime) + + func set(isMuted: Bool) +} + +extension CorePlayer { + var staticScope: Self.Type { + Self.self + } +} diff --git a/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/CorePlayerImpl.swift b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/CorePlayerImpl.swift new file mode 100644 index 00000000..0ab5184a --- /dev/null +++ b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/CorePlayerImpl.swift @@ -0,0 +1,183 @@ +import AVFoundation +import BasePublic +import Foundation + +final class CorePlayerImpl: CorePlayer { + static func isMIMETypeSupported(_ mimeType: String) -> Bool { + AVURLAsset.isPlayableExtendedMIMEType(mimeType) + } + + var videoEngine: VideoEngine { + VideoEngine(type: .avPlayer(player)) + } + + var playerStatusDidChange: Signal { + playerStatusPipe.signal + } + + var playbackStatusDidChange: Signal { + playbackStatusPipe.signal + } + + var playbackDidFail: Signal { + playerErrorPipe.signal + } + + var playbackDidFinish: Signal { + playbackFinishPipe.signal + } + + private let player: AVPlayer + + private let playerObservers = AutodisposePool() + private let itemObservers = AutodisposePool() + + private let playerStatusPipe = SignalPipe() + private let playbackStatusPipe = SignalPipe() + private let playerErrorPipe = SignalPipe() + private let playbackFinishPipe = SignalPipe() + + private var currentTimePipes = [TimeInterval: SignalPipe]() + + init() { + self.player = AVPlayer() + setup(player) + } + + private func resetPlayerObservers() { + playerObservers.drain() + } + + private func resetItemObservers() { + itemObservers.drain() + } + + func set(source: Video) { + let item = source.avPlayerItem + + resetItemObservers() + configureObservers(for: item) + + player.replaceCurrentItem(with: item) + } + + func play() { + player.play() + } + + func pause() { + player.pause() + } + + func seek(to position: CMTime) { + player.seek(to: position, toleranceBefore: .zero, toleranceAfter: .zero) + } + + func set(isMuted: Bool) { + player.isMuted = isMuted + } + + func periodicCurrentTimeSignal(interval: TimeInterval) -> Signal { + if let pipe = currentTimePipes[interval] { + return pipe.signal + } + + let pipe = SignalPipe() + currentTimePipes[interval] = pipe + + player + .addPeriodicCurrentTimeObserver(interval: interval) { pipe.send($0) } + .dispose(in: playerObservers) + + return pipe.signal + } + + private func setup(_ player: AVPlayer) { + player.actionAtItemEnd = .pause + player.automaticallyWaitsToMinimizeStalling = true + + configureObservers(for: player) + } + + private func configureObservers(for player: AVPlayer) { + weak var `self` = self + + observe(player, path: \.timeControlStatus) { _, timeControlStatus in + self?.playbackStatusDidChange(timeControlStatus.playbackStatus) + }.dispose(in: playerObservers) + + observe(player, path: \.status) { player, status in + let status = PlayerStatus(player: player, item: player.currentItem) + self?.playerStatusDidChange(status) + }.dispose(in: playerObservers) + } + + private func configureObservers(for item: AVPlayerItem) { + weak var `self` = self + + observe(item, path: \.status) { item, status in + guard let self = self, self.player.currentItem == item else { return } + + let status = PlayerStatus(player: self.player, item: item) + self.playerStatusDidChange(status) + }.dispose(in: itemObservers) + + NotificationCenter.default + .observe(item, name: .AVPlayerItemDidPlayToEndTime) { item, _ in + guard let self = self, self.player.currentItem == item else { return } + + self.playbackDidFinish(item) + }.dispose(in: itemObservers) + + NotificationCenter.default + .observe(item, name: .AVPlayerItemFailedToPlayToEndTime) { item, notification in + guard let self = self, self.player.currentItem == item else { return } + + let nserror = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? NSError + self.playbackDidFail(nserror.map(BasePlayerError.init) ?? UnknownPlayerError()) + }.dispose(in: itemObservers) + } + + private func playbackStatusDidChange(_ status: PlaybackStatus) { + playbackStatusPipe.send(status) + } + + private func playerStatusDidChange(_ status: PlayerStatus) { + playerStatusPipe.send(status) + + if status == .failed { + let playerError = (player.error as? NSError).map(BasePlayerError.init) + let itemError = (player.currentItem?.error as? NSError).map(BasePlayerError.init) + + playbackDidFail((playerError ?? itemError) ?? UnknownPlayerError()) + } + } + + private func playbackDidFail(_ error: PlayerError) { + playerErrorPipe.send(error) + } + + private func playbackDidFinish(_ item: AVPlayerItem) { + playbackFinishPipe.send() + } + + private func observe( + _ target: Target, + path: KeyPath, + onChange: @escaping (Target, Prop) -> Void + ) -> Disposable { + let observer = target.observe(path, options: [.new, .initial]) { object, _ in + onChange(object, object[keyPath: path]) + } + + return Disposable { + observer.invalidate() + } + } +} + +private extension Video { + var avPlayerItem: AVPlayerItem { + AVPlayerItem(url: url) + } +} diff --git a/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Helpers/AVPlayer+CurrentTimeObserver.swift b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Helpers/AVPlayer+CurrentTimeObserver.swift new file mode 100644 index 00000000..ca76d383 --- /dev/null +++ b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Helpers/AVPlayer+CurrentTimeObserver.swift @@ -0,0 +1,38 @@ +import AVFoundation +import BasePublic +import Foundation + +extension AVPlayer { + func addPeriodicCurrentTimeObserver( + interval: TimeInterval, + queue: DispatchQueue? = nil, + handler: @escaping (TimeInterval) -> Void + ) -> Disposable { + let observer = addPeriodicTimeObserver( + forInterval: CMTime(seconds: interval, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), + queue: queue, + using: { time in + time.safeSeconds.map { handler($0.notNegative) } + } + ) + + return Disposable { [weak self] in + self?.removeTimeObserver(observer) + } + } +} + +extension CMTime { + var safeSeconds: Double? { + if !isValid || isIndefinite { + return nil + } + return seconds + } +} + +extension BinaryFloatingPoint { + var notNegative: Self { + self < 0 ? 0 : self + } +} diff --git a/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Helpers/AVPlayerTimeControlStatus+Extensions.swift b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Helpers/AVPlayerTimeControlStatus+Extensions.swift new file mode 100644 index 00000000..fe6cdbcd --- /dev/null +++ b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Helpers/AVPlayerTimeControlStatus+Extensions.swift @@ -0,0 +1,17 @@ +import AVFoundation + +extension AVPlayer.TimeControlStatus { + var playbackStatus: PlaybackStatus { + switch self { + case .paused: + return .paused + case .waitingToPlayAtSpecifiedRate: + return .buffering + case .playing: + return .playing + @unknown default: + assertionFailure() + return .paused + } + } +} diff --git a/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Helpers/NotificationCenter+Extensions.swift b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Helpers/NotificationCenter+Extensions.swift new file mode 100644 index 00000000..e161428e --- /dev/null +++ b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Helpers/NotificationCenter+Extensions.swift @@ -0,0 +1,46 @@ +import BasePublic +import Foundation + +extension NotificationCenter { + func observe( + name: Notification.Name, + queue: OperationQueue? = nil, + block: @escaping (Notification) -> Void + ) -> Disposable { + + let observer = addObserver( + forName: name, + object: nil, + queue: queue, + using: { notification in + block(notification) + } + ) + + return Disposable { [weak self] in + self?.removeObserver(observer) + } + } + + func observe( + _ target: Target, + name: Notification.Name, + queue: OperationQueue? = nil, + block: @escaping (Target, Notification) -> Void + ) -> Disposable { + + let observer = addObserver( + forName: name, + object: target, + queue: queue, + using: { notification in + guard let target = notification.object as? Target else { return } + block(target, notification) + } + ) + + return Disposable { [weak self] in + self?.removeObserver(observer) + } + } +} diff --git a/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Models/PlaybackStatus.swift b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Models/PlaybackStatus.swift new file mode 100644 index 00000000..6ae81c70 --- /dev/null +++ b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Models/PlaybackStatus.swift @@ -0,0 +1,5 @@ +enum PlaybackStatus { + case playing + case paused + case buffering +} diff --git a/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Models/PlayerError.swift b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Models/PlayerError.swift new file mode 100644 index 00000000..5737336f --- /dev/null +++ b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Models/PlayerError.swift @@ -0,0 +1,17 @@ +import Foundation + +protocol PlayerError: Error {} + +struct BasePlayerError: PlayerError { + let error: NSError + + init(_ error: NSError) { + self.error = error + } +} + +struct UnknownPlayerError: PlayerError { + var description: String { + "Something went wrong" + } +} diff --git a/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Models/PlayerStatus.swift b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Models/PlayerStatus.swift new file mode 100644 index 00000000..4fb9fce6 --- /dev/null +++ b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/Models/PlayerStatus.swift @@ -0,0 +1,63 @@ +import AVFoundation + +public enum PlayerStatus { + case unknown + case readyToPlay + case failed +} + +extension PlayerStatus { + init(playerStatus: PlayerStatus, itemStatus: PlayerStatus?) { + switch (playerStatus, itemStatus) { + case (.readyToPlay, .readyToPlay): + self = .readyToPlay + case (.failed, _), (_, .failed): + self = .failed + case (.unknown, _), (_, .unknown): + self = .unknown + default: + self = .unknown + } + } +} + +extension PlayerStatus { + init(player: AVPlayer, item: AVPlayerItem?) { + self.init( + playerStatus: player.status.asPlayerStatus, + itemStatus: item?.status.asPlayerStatus + ) + } +} + +extension AVPlayer.Status { + var asPlayerStatus: PlayerStatus { + switch self { + case .readyToPlay: + return .readyToPlay + case .failed: + return .failed + case .unknown: + return .unknown + @unknown default: + assertionFailure() + return .unknown + } + } +} + +extension AVPlayerItem.Status { + var asPlayerStatus: PlayerStatus { + switch self { + case .readyToPlay: + return .readyToPlay + case .failed: + return .failed + case .unknown: + return .unknown + @unknown default: + assertionFailure() + return .unknown + } + } +} diff --git a/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/VideoEngineProvider.swift b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/VideoEngineProvider.swift new file mode 100644 index 00000000..2bada8fe --- /dev/null +++ b/LayoutKit/LayoutKit/Blocks/Video/CorePlayer/VideoEngineProvider.swift @@ -0,0 +1,13 @@ +import AVFoundation + +protocol VideoEngineProvider { + var videoEngine: VideoEngine { get } +} + +struct VideoEngine { + enum EngineType { + case avPlayer(AVPlayer) + } + + let type: EngineType +} diff --git a/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayer.swift b/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayer.swift index 37a21ed1..69d40667 100644 --- a/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayer.swift +++ b/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayer.swift @@ -2,99 +2,122 @@ import AVFoundation import BasePublic import Foundation -public final class DefaultPlayer: Player { - let player = AVQueuePlayer() - private var currentItem: AVPlayerItem? { - didSet { - player.replaceCurrentItem(with: currentItem) - } +final class DefaultPlayer: Player { + private let eventPipe = SignalPipe() + var signal: Signal { + eventPipe.signal } - private var playerLooper: AVPlayerLooper? - private let eventPipe = SignalPipe() - private var statusObserving: NSKeyValueObservation? - private var config: PlaybackConfig? - - public func set(data: PlayerData, config: PlaybackConfig) { - self.config = config - configureObservers() - currentItem = data.playerItem - - if case .video = data { - if config.repeatable { - currentItem.flatMap { playerLooper = AVPlayerLooper(player: player, templateItem: $0) } - } else { - playerLooper = nil - } - } - player.isMuted = config.isMuted + private let player: CorePlayer + private var context: SourceContext? + + private let playerObservers = AutodisposePool() + private let playbackConfigObservers = AutodisposePool() + + init(player: CorePlayer? = nil) { + self.player = player ?? CorePlayerImpl() + configureObservers(for: self.player) } - private func configureObservers() { - player.addPeriodicTimeObserver( - forInterval: CMTimeMake(value: 1000, timescale: 1000), - queue: .main - ) { [weak self] time in - guard let self = self else { return } - self.eventPipe.send(.currentTimeUpdate(time: Int(time.seconds * 1000))) + func set(data: VideoData, config: PlaybackConfig) { + context = SourceContext(videoData: data, playbackConfig: config) + guard let source = data.getSupportedVideo(player.staticScope.isMIMETypeSupported) else { + assertionFailure("Empty source") + eventPipe.send(.fatal) + return } - NotificationCenter.default - .addObserver( - self, - selector: #selector(playerDidFinishPlaying), - name: .AVPlayerItemDidPlayToEndTime, - object: player.currentItem - ) - - statusObserving = player.observe(\.status) { [weak self] player, _ in - switch player.status { - case .readyToPlay: - player.seek( - to: self?.config?.startPosition ?? .zero, - toleranceBefore: .zero, - toleranceAfter: .zero - ) - if self?.config?.autoPlay == true { - player.play() + player.set(source: source) + handle(config: config) + } + + func play() { + player.play() + } + + func pause() { + player.pause() + } + + func set(isMuted: Bool) { + player.set(isMuted: isMuted) + } + + func seek(to position: CMTime) { + player.seek(to: position) + } + + private func handle(config: PlaybackConfig) { + playbackConfigObservers.drain() + + if config.repeatable { + player + .playbackDidFinish + .addObserver { [weak self] _ in + self?.player.seek(to: .zero) + self?.player.play() } - case .failed: - self?.eventPipe.send(.error) - case .unknown: - break - default: - break - } + .dispose(in: playbackConfigObservers) } - } - @objc func playerDidFinishPlaying(notification _: NSNotification) { - if config?.repeatable == false { - eventPipe.send(.videoOver) + player.set(isMuted: config.isMuted) + config.autoPlay ? player.play() : player.pause() + + if config.startPosition != .zero { + player.seek(to: config.startPosition) } } - public var signal: Signal { - eventPipe.signal - } + private func configureObservers(for player: CorePlayer) { + weak var `self` = self - public func play() { - player.play() + player + .playbackStatusDidChange + .map { playbackStatus -> PlayerEvent in + switch playbackStatus { + case .playing: + return .play + case .paused: + return .pause + case .buffering: + return .buffering + } + } + .addObserver { self?.eventPipe.send($0) } + .dispose(in: playerObservers) + + player + .playbackDidFinish + .addObserver { self?.eventPipe.send(.end) } + .dispose(in: playerObservers) + + player + .playbackDidFail + .addObserver { _ in self?.eventPipe.send(.fatal) } + .dispose(in: playerObservers) + + player + .periodicCurrentTimeSignal(interval: 1) + .addObserver { self?.eventPipe.send(.currentTimeUpdate(Int($0 * 1000))) } + .dispose(in: playerObservers) } +} - public func pause() { - player.pause() +extension DefaultPlayer: VideoEngineProvider { + var videoEngine: VideoEngine { + player.videoEngine } +} - public func set(isMuted: Bool) { - player.isMuted = isMuted +private extension DefaultPlayer { + struct SourceContext { + var videoData: VideoData + var playbackConfig: PlaybackConfig } +} - public func seek(to position: CMTime) { - player.seek( - to: position, - toleranceBefore: .zero, - toleranceAfter: .zero - ) +private extension VideoData { + func getSupportedVideo(_ mimeTypeChecker: (String) -> Bool) -> Video? { + videos.first { mimeTypeChecker($0.mimeType) } } } diff --git a/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayerFactory.swift b/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayerFactory.swift index 8e6dda71..98a5aa38 100644 --- a/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayerFactory.swift +++ b/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayerFactory.swift @@ -4,7 +4,7 @@ import Foundation import UIKit public final class DefaultPlayerFactory: PlayerFactory { - public func makePlayer(data: PlayerData?, config: PlaybackConfig?) -> Player { + public func makePlayer(data: VideoData?, config: PlaybackConfig?) -> Player { let player = DefaultPlayer() guard let config, let data else { return player } player.set(data: data, config: config) diff --git a/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayerView.swift b/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayerView.swift index fa963753..ef325606 100644 --- a/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayerView.swift +++ b/LayoutKit/LayoutKit/Blocks/Video/DefaultPlayer/DefaultPlayerView.swift @@ -3,11 +3,15 @@ import UIKit public final class DefaultPlayerView: UIView, PlayerView { public func attach(player: Player) { - guard let player = (player as? DefaultPlayer)?.player else { + guard let engine = (player as? VideoEngineProvider)?.videoEngine else { assertionFailure("Can't use DefaultPlayerView with not with DefaultPlayer") return } - player.bind(to: playerLayer) + + switch engine.type { + case let .avPlayer(player): + player.bind(to: playerLayer) + } } private var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer } diff --git a/LayoutKit/LayoutKit/Blocks/Video/Player.swift b/LayoutKit/LayoutKit/Blocks/Video/Player.swift index 60fd974f..70e000c6 100644 --- a/LayoutKit/LayoutKit/Blocks/Video/Player.swift +++ b/LayoutKit/LayoutKit/Blocks/Video/Player.swift @@ -3,33 +3,21 @@ import Foundation import AVFoundation public protocol Player { - var signal: Signal { get } + var signal: Signal { get } - func set(data: PlayerData, config: PlaybackConfig) + func set(data: VideoData, config: PlaybackConfig) func play() func pause() func set(isMuted: Bool) func seek(to position: CMTime) } -public enum Event { - case bufferOver - case error - case videoOver - case currentTimeUpdate(time: Int) - case started -} - -public enum PlayerData: Equatable { - case video(Video) - case stream(URL) +public enum PlayerEvent { + case pause + case buffering + case play + case end + case fatal - public var playerItem: AVPlayerItem { - switch self { - case .video(let video): - return AVPlayerItem(url: video.url) - case .stream(let url): - return AVPlayerItem(url: url) - } - } + case currentTimeUpdate(_ ms: Int) } diff --git a/LayoutKit/LayoutKit/Blocks/Video/PlayerFactory.swift b/LayoutKit/LayoutKit/Blocks/Video/PlayerFactory.swift index 2ca4252e..bf14b689 100644 --- a/LayoutKit/LayoutKit/Blocks/Video/PlayerFactory.swift +++ b/LayoutKit/LayoutKit/Blocks/Video/PlayerFactory.swift @@ -2,7 +2,7 @@ import Foundation import CoreMedia public protocol PlayerFactory { - func makePlayer(data: PlayerData?, config: PlaybackConfig?) -> Player + func makePlayer(data: VideoData?, config: PlaybackConfig?) -> Player func makePlayerView() -> PlayerView } @@ -27,7 +27,7 @@ public struct PlaybackConfig: Equatable { self.settingsPayload = settingsPayload } - static let `default` = PlaybackConfig( + public static let `default` = PlaybackConfig( autoPlay: true, repeatable: false, isMuted: false, diff --git a/LayoutKit/LayoutKit/Blocks/Video/VideoData.swift b/LayoutKit/LayoutKit/Blocks/Video/VideoData.swift index 0240bcac..057846bc 100644 --- a/LayoutKit/LayoutKit/Blocks/Video/VideoData.swift +++ b/LayoutKit/LayoutKit/Blocks/Video/VideoData.swift @@ -1,25 +1,30 @@ import Foundation -public enum VideoData: Equatable { - case stream(URL) - case video([Video]) +public struct VideoData: Equatable { + public let videos: [Video] + + public init(videos: [Video] = []) { + self.videos = videos + } } public struct Video: Equatable { - let url: URL - let resolution: CGSize - let codec: String? - let mimeType: String? + public let url: URL + + public let resolution: CGSize? + public let bitrate: Double? + + public let mimeType: String public init( url: URL, - resolution: CGSize = .zero, - codec: String? = nil, + resolution: CGSize? = nil, + bitrate: Double? = nil, mimeType: String? = nil ) { self.url = url + self.bitrate = bitrate self.resolution = resolution - self.codec = codec - self.mimeType = mimeType + self.mimeType = mimeType ?? "" } } diff --git a/LayoutKit/LayoutKit/Blocks/VideoBlockLegacy.swift b/LayoutKit/LayoutKit/Blocks/VideoBlockLegacy.swift index 56d14871..b7433bf0 100644 --- a/LayoutKit/LayoutKit/Blocks/VideoBlockLegacy.swift +++ b/LayoutKit/LayoutKit/Blocks/VideoBlockLegacy.swift @@ -8,11 +8,11 @@ import CommonCorePublic public final class VideoBlockLegacy: BlockWithTraits { public struct VideoAssetHolder { let url: URL - let playerItem: AVPlayerItem + let playerItem: Future public init( url: URL, - playerItem: AVPlayerItem + playerItem: Future ) { self.url = url self.playerItem = playerItem diff --git a/LayoutKit/LayoutKit/UI/Blocks/VideoBlock+UIViewRenderableBlock.swift b/LayoutKit/LayoutKit/UI/Blocks/VideoBlock+UIViewRenderableBlock.swift index 27030044..82a30e50 100644 --- a/LayoutKit/LayoutKit/UI/Blocks/VideoBlock+UIViewRenderableBlock.swift +++ b/LayoutKit/LayoutKit/UI/Blocks/VideoBlock+UIViewRenderableBlock.swift @@ -38,7 +38,6 @@ private final class VideoBlockView: BlockView { } private var model: VideoBlockViewModel = .zero - private var needsUpdatePlayerData: Bool = true private var playerSignal: Disposable? private lazy var player: Player? = { @@ -48,17 +47,18 @@ private final class VideoBlockView: BlockView { ) playerSignal = player?.signal.addObserver { [weak self] event in - guard let self = self else { return } - switch event { - case let .currentTimeUpdate(time: time): - self.model.elapsedTime?.setValue(time, responder: self) - case .videoOver: - self.observer?.elementStateChanged(self.state, forPath: self.model.path) - self.model.endActions.perform(sendingFrom: self) - case .bufferOver, .error: - break - case .started: - break + onMainThread { + guard let self = self else { return } + + switch event { + case let .currentTimeUpdate(time): + self.model.elapsedTime?.setValue(Int(time), responder: self) + case .end: + self.observer?.elementStateChanged(self.state, forPath: self.model.path) + self.model.endActions.perform(sendingFrom: self) + default: // TODO + break + } } } @@ -89,29 +89,12 @@ private final class VideoBlockView: BlockView { var playerFactory: PlayerFactory? var effectiveBackgroundColor: UIColor? - private func updatePlayerData() { - needsUpdatePlayerData = false - switch model.videoData { - case let .stream(url): - player?.set(data: .stream(url), config: model.playbackConfig) - case let .video(video): - guard let video = video.min(by: { lhs, rhs in - abs(lhs.resolution.area - (videoView?.bounds.size.area ?? 0)) < - abs(rhs.resolution.area - (videoView?.bounds.size.area ?? 0)) - }) else { - return - } - player?.set(data: .video(video), config: model.playbackConfig) - } - } - func configure(with model: VideoBlockViewModel) { let oldValue = self.model self.model = model if model.videoData != oldValue.videoData { - needsUpdatePlayerData = true - setNeedsLayout() + player?.set(data: model.videoData, config: model.playbackConfig) } if oldValue.elapsedTime != model.elapsedTime { @@ -122,9 +105,6 @@ private final class VideoBlockView: BlockView { override func layoutSubviews() { super.layoutSubviews() videoView?.frame = bounds - if needsUpdatePlayerData, videoView?.frame != .zero { - updatePlayerData() - } } func onVisibleBoundsChanged(from _: CGRect, to _: CGRect) {} @@ -132,7 +112,7 @@ private final class VideoBlockView: BlockView { extension VideoBlockViewModel { fileprivate static let zero: Self = VideoBlockViewModel( - videoData: .video([]), + videoData: VideoData(videos: []), playbackConfig: .default, path: .init("") ) diff --git a/LayoutKit/LayoutKit/UI/Blocks/VideoBlockLegacy+UIViewRenderableBlock.swift b/LayoutKit/LayoutKit/UI/Blocks/VideoBlockLegacy+UIViewRenderableBlock.swift index 7e85004f..799620de 100644 --- a/LayoutKit/LayoutKit/UI/Blocks/VideoBlockLegacy+UIViewRenderableBlock.swift +++ b/LayoutKit/LayoutKit/UI/Blocks/VideoBlockLegacy+UIViewRenderableBlock.swift @@ -29,7 +29,12 @@ extension VideoBlockLegacy { private final class VideoBlockLegacyView: BlockView { var videoAssetHolder: VideoBlockLegacy.VideoAssetHolder! { didSet { - configurePlayer(with: videoAssetHolder.playerItem) + let actualURL = videoAssetHolder.url + videoAssetHolder.playerItem.map { (actualURL, $0) }.resolved { [weak self] url, playerItem in + if self?.videoAssetHolder.url == url { + self?.configurePlayer(with: playerItem) + } + } } } diff --git a/Specs/BasePublic/25.1.0/BasePublic.podspec b/Specs/BasePublic/25.1.0/BasePublic.podspec new file mode 100644 index 00000000..bd8503d0 --- /dev/null +++ b/Specs/BasePublic/25.1.0/BasePublic.podspec @@ -0,0 +1,23 @@ +Pod::Spec.new do |s| + s.name = 'BasePublic' + s.version = '25.1.0' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '11.0' } + + s.dependency 'BaseTinyPublic', s.version.to_s + s.dependency 'BaseUIPublic', s.version.to_s + + s.source_files = [ + 'Core/BasePublic/**/*' + ] +end diff --git a/Specs/BaseTinyPublic/25.1.0/BaseTinyPublic.podspec b/Specs/BaseTinyPublic/25.1.0/BaseTinyPublic.podspec new file mode 100644 index 00000000..5efa44e7 --- /dev/null +++ b/Specs/BaseTinyPublic/25.1.0/BaseTinyPublic.podspec @@ -0,0 +1,20 @@ +Pod::Spec.new do |s| + s.name = 'BaseTinyPublic' + s.version = '25.1.0' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '11.0' } + + s.source_files = [ + 'Core/BaseTinyPublic/**/*' + ] +end diff --git a/Specs/BaseUIPublic/25.1.0/BaseUIPublic.podspec b/Specs/BaseUIPublic/25.1.0/BaseUIPublic.podspec new file mode 100644 index 00000000..04f7ea89 --- /dev/null +++ b/Specs/BaseUIPublic/25.1.0/BaseUIPublic.podspec @@ -0,0 +1,22 @@ +Pod::Spec.new do |s| + s.name = 'BaseUIPublic' + s.version = '25.1.0' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '11.0' } + + s.dependency 'BaseTinyPublic', s.version.to_s + + s.source_files = [ + 'Core/BaseUIPublic/**/*' + ] +end diff --git a/Specs/CommonCorePublic/25.1.0/CommonCorePublic.podspec b/Specs/CommonCorePublic/25.1.0/CommonCorePublic.podspec new file mode 100644 index 00000000..1b9f7cf2 --- /dev/null +++ b/Specs/CommonCorePublic/25.1.0/CommonCorePublic.podspec @@ -0,0 +1,22 @@ +Pod::Spec.new do |s| + s.name = 'CommonCorePublic' + s.version = '25.1.0' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '11.0' } + + s.dependency 'BasePublic', s.version.to_s + + s.source_files = [ + 'Core/CommonCorePublic/**/*' + ] +end diff --git a/Specs/DivKit/25.1.0/DivKit.podspec b/Specs/DivKit/25.1.0/DivKit.podspec new file mode 100644 index 00000000..3263b92c --- /dev/null +++ b/Specs/DivKit/25.1.0/DivKit.podspec @@ -0,0 +1,25 @@ +Pod::Spec.new do |s| + s.name = 'DivKit' + s.version = '25.1.0' + s.summary = 'DivKit framework' + s.description = 'DivKit is a backend-driven UI framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '11.0' } + + s.dependency 'CommonCorePublic', s.version.to_s + s.dependency 'LayoutKit', s.version.to_s + s.dependency 'NetworkingPublic', s.version.to_s + s.dependency 'Serialization', s.version.to_s + + s.source_files = [ + 'DivKit/**/*' + ] +end diff --git a/Specs/DivKitExtensions/25.1.0/DivKitExtensions.podspec b/Specs/DivKitExtensions/25.1.0/DivKitExtensions.podspec new file mode 100644 index 00000000..207b5bd4 --- /dev/null +++ b/Specs/DivKitExtensions/25.1.0/DivKitExtensions.podspec @@ -0,0 +1,22 @@ +Pod::Spec.new do |s| + s.name = 'DivKitExtensions' + s.version = '25.1.0' + s.summary = 'DivKit framework extensions' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '11.0' } + + s.dependency 'DivKit', s.version.to_s + + s.source_files = [ + 'DivKitExtensions/**/*' + ] +end diff --git a/Specs/LayoutKit/25.1.0/LayoutKit.podspec b/Specs/LayoutKit/25.1.0/LayoutKit.podspec new file mode 100644 index 00000000..b2052014 --- /dev/null +++ b/Specs/LayoutKit/25.1.0/LayoutKit.podspec @@ -0,0 +1,23 @@ +Pod::Spec.new do |s| + s.name = 'LayoutKit' + s.version = '25.1.0' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '11.0' } + + s.dependency 'CommonCorePublic', s.version.to_s + s.dependency 'LayoutKitInterface', s.version.to_s + + s.source_files = [ + 'LayoutKit/LayoutKit/**/*' + ] +end diff --git a/Specs/LayoutKitInterface/25.1.0/LayoutKitInterface.podspec b/Specs/LayoutKitInterface/25.1.0/LayoutKitInterface.podspec new file mode 100644 index 00000000..3491b389 --- /dev/null +++ b/Specs/LayoutKitInterface/25.1.0/LayoutKitInterface.podspec @@ -0,0 +1,24 @@ +Pod::Spec.new do |s| + s.name = 'LayoutKitInterface' + s.version = '25.1.0' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '11.0' } + + s.dependency 'BasePublic', s.version.to_s + s.dependency 'BaseTinyPublic', s.version.to_s + s.dependency 'BaseUIPublic', s.version.to_s + + s.source_files = [ + 'LayoutKit/Interface/**/*' + ] +end diff --git a/Specs/NetworkingPublic/25.1.0/NetworkingPublic.podspec b/Specs/NetworkingPublic/25.1.0/NetworkingPublic.podspec new file mode 100644 index 00000000..81a8f158 --- /dev/null +++ b/Specs/NetworkingPublic/25.1.0/NetworkingPublic.podspec @@ -0,0 +1,22 @@ +Pod::Spec.new do |s| + s.name = 'NetworkingPublic' + s.version = '25.1.0' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '11.0' } + + s.dependency 'BasePublic', s.version.to_s + + s.source_files = [ + 'Core/NetworkingPublic/**/*' + ] +end diff --git a/Specs/Serialization/25.1.0/Serialization.podspec b/Specs/Serialization/25.1.0/Serialization.podspec new file mode 100644 index 00000000..fb3cf532 --- /dev/null +++ b/Specs/Serialization/25.1.0/Serialization.podspec @@ -0,0 +1,23 @@ +Pod::Spec.new do |s| + s.name = 'Serialization' + s.version = '25.1.0' + s.summary = 'Serialization' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '11.0' } + + s.dependency 'CommonCorePublic', s.version.to_s + + s.source_files = [ + 'Serialization/**/*' + ] +end