273 lines
7.9 KiB
Swift
273 lines
7.9 KiB
Swift
|
|
struct VideoBlock: OrderedKeyBlockProcessor {
|
|
|
|
static let blockId: ContentBlock = .video
|
|
|
|
let content: Content
|
|
|
|
let results: PageGenerationResults
|
|
|
|
let language: ContentLanguage
|
|
|
|
init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
|
|
self.content = content
|
|
self.results = results
|
|
self.language = language
|
|
}
|
|
|
|
func process(_ arguments: [(key: Key, value: String)], markdown: Substring) -> String {
|
|
var options: [Option] = []
|
|
var sources: [Source] = []
|
|
|
|
for (key, value) in arguments {
|
|
guard let sourceType = key.sourceType else {
|
|
guard let option = makeOption(key: key, value: value) else {
|
|
invalid(markdown)
|
|
continue
|
|
}
|
|
options.append(option)
|
|
continue
|
|
}
|
|
let fileId = value.removingSurroundingQuotes
|
|
guard let file = content.file(fileId) else {
|
|
results.missing(file: fileId, source: "Video Block: \(key)")
|
|
continue
|
|
}
|
|
guard file.type.isVideo else {
|
|
invalid(markdown)
|
|
continue
|
|
}
|
|
results.require(file: file)
|
|
let source = Source(file: file, type: sourceType)
|
|
sources.append(source)
|
|
}
|
|
|
|
guard !sources.isEmpty else {
|
|
invalid(markdown)
|
|
return ""
|
|
}
|
|
|
|
return VersionedVideo(sources: sources, options: options).content
|
|
}
|
|
|
|
private func makeOption(key: Key, value: String) -> Option? {
|
|
switch key {
|
|
case .controls: return .controls
|
|
case .autoplay: return .autoplay
|
|
case .muted: return .muted
|
|
case .loop: return .loop
|
|
case .playsinline: return .playsinline
|
|
default: break
|
|
}
|
|
|
|
let value = value.removingSurroundingQuotes
|
|
|
|
guard value != "" else {
|
|
return nil
|
|
}
|
|
switch key {
|
|
case .height:
|
|
guard let height = Int(value) else {
|
|
return nil
|
|
}
|
|
return .height(height)
|
|
|
|
case .width:
|
|
guard let width = Int(value) else {
|
|
return nil
|
|
}
|
|
return .width(width)
|
|
case .preload:
|
|
guard let preloadOption = Option.Preload(rawValue: value) else {
|
|
return nil
|
|
}
|
|
return .preload(preloadOption)
|
|
case .poster:
|
|
guard let image = content.image(value) else {
|
|
results.missing(file: value, source: "Video Block: poster")
|
|
return nil
|
|
}
|
|
let width = 2 * content.settings.pages.contentWidth
|
|
let version = image.imageVersion(width: width, height: width, type: .jpg)
|
|
results.require(image: version)
|
|
return .poster(image: version.outputPath)
|
|
case .src:
|
|
guard let file = content.file(value) else {
|
|
results.missing(file: value, source: "Video Block: src")
|
|
return nil
|
|
}
|
|
results.warning("Use 'h264' and 'h265' instead of 'src'")
|
|
return .src(file.absoluteUrl)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
extension VideoBlock {
|
|
|
|
enum Key: String {
|
|
|
|
/// The H264 video file to use
|
|
case h264
|
|
|
|
/// The H265 video file to use
|
|
case h265
|
|
|
|
/// The WebM video file to use
|
|
case webm
|
|
|
|
// MARK: Video options
|
|
|
|
/// Specifies that video controls should be displayed (such as a play/pause button etc).
|
|
case controls
|
|
|
|
/// Specifies that the video will start playing as soon as it is ready
|
|
case autoplay
|
|
|
|
/// Specifies that the video will start over again, every time it is finished
|
|
case loop
|
|
|
|
/// Specifies that the audio output of the video should be muted
|
|
case muted
|
|
|
|
/// Mobile browsers will play the video right where it is instead of the default, which is to open it up fullscreen while it plays
|
|
case playsinline
|
|
|
|
/// Sets the height of the video player
|
|
case height
|
|
|
|
/// Sets the width of the video player
|
|
case width
|
|
|
|
/// Specifies if and how the author thinks the video should be loaded when the page loads
|
|
case preload
|
|
|
|
/// Specifies an image to be shown while the video is downloading, or until the user hits the play button
|
|
case poster
|
|
|
|
/// Specifies the URL of the video file
|
|
case src
|
|
|
|
var isOption: Bool {
|
|
switch self {
|
|
case .controls, .autoplay, .loop, .muted, .playsinline, .height, .width, .preload, .poster, .src:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
var sourceType: SourceType? {
|
|
switch self {
|
|
case .h264: .h264
|
|
case .h265: .h265
|
|
case .webm: .webm
|
|
default: nil
|
|
}
|
|
}
|
|
}
|
|
|
|
enum SourceType {
|
|
case h264
|
|
case h265
|
|
case webm
|
|
|
|
var order: Int {
|
|
switch self {
|
|
case .h265: 1
|
|
case .webm: 2
|
|
case .h264: 3
|
|
}
|
|
}
|
|
|
|
var mimeType: String {
|
|
switch self {
|
|
case .h265, .h264: "video/mp4"
|
|
case .webm: "video/webm"
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Source {
|
|
|
|
let file: FileResource
|
|
|
|
let type: SourceType
|
|
}
|
|
}
|
|
|
|
extension VideoBlock {
|
|
|
|
enum Option {
|
|
|
|
/// Specifies that video controls should be displayed (such as a play/pause button etc).
|
|
case controls
|
|
|
|
/// Specifies that the video will start playing as soon as it is ready
|
|
case autoplay
|
|
|
|
/// Specifies that the video will start over again, every time it is finished
|
|
case loop
|
|
|
|
/// Specifies that the audio output of the video should be muted
|
|
case muted
|
|
|
|
/// Mobile browsers will play the video right where it is instead of the default, which is to open it up fullscreen while it plays
|
|
case playsinline
|
|
|
|
/// Sets the height of the video player
|
|
case height(Int)
|
|
|
|
/// Sets the width of the video player
|
|
case width(Int)
|
|
|
|
/// Specifies if and how the author thinks the video should be loaded when the page loads
|
|
case preload(Preload)
|
|
|
|
/// Specifies an image to be shown while the video is downloading, or until the user hits the play button
|
|
case poster(image: String)
|
|
|
|
/// Specifies the URL of the video file
|
|
case src(String)
|
|
|
|
var rawValue: String {
|
|
switch self {
|
|
case .controls: return "controls"
|
|
case .autoplay: return "autoplay"
|
|
case .muted: return "muted"
|
|
case .loop: return "loop"
|
|
case .playsinline: return "playsinline"
|
|
case .height(let height): return "height='\(height)'"
|
|
case .width(let width): return "width='\(width)'"
|
|
case .preload(let option): return "preload='\(option)'"
|
|
case .poster(let image): return "poster='\(image)'"
|
|
case .src(let url): return "src='\(url)'"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension VideoBlock.Option {
|
|
|
|
/**
|
|
The `preload` attribute specifies if and how the author thinks that the video should be loaded when the page loads.
|
|
|
|
The `preload` attribute allows the author to provide a hint to the browser about what he/she thinks will lead to the best user experience.
|
|
This attribute may be ignored in some instances.
|
|
|
|
Note: The `preload` attribute is ignored if `autoplay` is present.
|
|
*/
|
|
enum Preload: String {
|
|
|
|
/// The author thinks that the browser should load the entire video when the page loads
|
|
case auto
|
|
|
|
/// The author thinks that the browser should load only metadata when the page loads
|
|
case metadata
|
|
|
|
/// The author thinks that the browser should NOT load the video when the page loads
|
|
case none
|
|
}
|
|
}
|