Skip to content

Commit

Permalink
Refactor receivedEvent to receivedPlayerEvent
Browse files Browse the repository at this point in the history
  • Loading branch information
SvenTiigi committed Feb 2, 2025
1 parent 0e03512 commit a592c43
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 122 deletions.
98 changes: 98 additions & 0 deletions Sources/API/YouTubePlayer+API.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import Combine
import Foundation
import OSLog

// MARK: - Event Publisher

public extension YouTubePlayer {

/// A Publisher that emits a received ``YouTubePlayer/Event``
var eventPublisher: some Publisher<Event, Never> {
self.webView
.eventSubject
.compactMap(\.playerEvent)
.receive(on: DispatchQueue.main)
}

}

// MARK: - Evaluate

public extension YouTubePlayer {

/// Evaluates the JavaScript and converts its response.
/// - Parameters:
/// - javaScript: The JavaScript to evaluate.
/// - converter: The response converter.
func evaluate<Response>(
javaScript: JavaScript,
converter: JavaScriptEvaluationResponseConverter<Response>
) async throws(APIError) -> Response {
try await self.webView.evaluate(
javaScript: javaScript,
converter: converter
)
}

/// Evaluates the JavaScript.
/// - Parameter javaScript: The JavaScript to evaluate.
func evaluate(
javaScript: JavaScript
) async throws(APIError) {
try await self.evaluate(
javaScript: javaScript,
converter: .void
)
}

}

// MARK: - Reload

public extension YouTubePlayer {

/// Reloads the YouTube player.
func reload() async throws(Swift.Error) {
// Destroy the player and discard the error
try? await self.evaluate(
javaScript: .youTubePlayer(
functionName: "destroy"
)
)
// Reload
try self.webView.load()
// Await new ready or error state
for await state in self.stateSubject.dropFirst().values {
// Swithc on state
switch state {
case .ready:
// Success return out of function
return
case .error(let error):
// Throw error
throw error
default:
// Continue with next state
continue
}
}
}

}

// MARK: - Logger

public extension YouTubePlayer {

/// Returns a new logger instance if logging is enabled via ``YouTubePlayer/isLoggingEnabled``
func logger() -> Logger? {
guard self.isLoggingEnabled else {
return nil
}
return .init(
subsystem: "YouTubePlayer",
category: .init(describing: self.id)
)
}

}
10 changes: 5 additions & 5 deletions Sources/WebView/YouTubePlayerWebView+Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ extension YouTubePlayerWebView {
/// A YouTubePlayerWebView Event
enum Event: Sendable {
/// Received ``YouTubePlayer/Event``
case receivedEvent(YouTubePlayer.Event)
case receivedPlayerEvent(YouTubePlayer.Event)
/// Did fail provisional navigation
case didFailProvisionalNavigation(Error)
/// Did fail navigation
Expand All @@ -18,14 +18,14 @@ extension YouTubePlayerWebView {

}

// MARK: - Event
// MARK: - Player Event

extension YouTubePlayerWebView.Event {

/// The received ``YouTubePlayer/Event``, if available.
var event: YouTubePlayer.Event? {
if case .receivedEvent(let event) = self {
return event
var playerEvent: YouTubePlayer.Event? {
if case .receivedPlayerEvent(let playerEvent) = self {
return playerEvent
} else {
return nil
}
Expand Down
22 changes: 11 additions & 11 deletions Sources/WebView/YouTubePlayerWebView+WKNavigationDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ extension YouTubePlayerWebView: WKNavigationDelegate {
// Allow navigation action
return .allow
}
// Check if the scheme matches the JavaScript event callback URL scheme and if the host is a known event name
// Check if the scheme matches the JavaScript event callback URL scheme and if the host is a known player event name
if url.scheme == self.player?.configuration.htmlBuilder.youTubePlayerEventCallbackURLScheme,
let eventName = url.host.flatMap(YouTubePlayer.Event.Name.init) {
// Initialize event
let event = YouTubePlayer.Event(
name: eventName,
let playerEventName = url.host.flatMap(YouTubePlayer.Event.Name.init) {
// Initialize player event
let playerEvent = YouTubePlayer.Event(
name: playerEventName,
data: URLComponents(
url: url,
resolvingAgainstBaseURL: true
Expand All @@ -78,16 +78,16 @@ extension YouTubePlayerWebView: WKNavigationDelegate {
.first { $0.name == self.player?.configuration.htmlBuilder.youTubePlayerEventCallbackDataParameterName }
.flatMap(YouTubePlayer.Event.Data.init)
)
// Check if a logger is available and ensure the event is not `.video`.
// The `.videoProgress` event fires every second and is therefore explicitly excluded from logging.
// Check if a logger is available and ensure event name is not equal to `videoProgress` and `loadProgress`
// Those two events are explicitly excluded because they occur in a high frequency.
if let logger = self.player?.logger(),
event.name != .videoProgress && event.name != .loadProgress {
playerEventName != .videoProgress && playerEventName != .loadProgress {
// Log received JavaScript event
logger.debug("Received YouTube Player Event\n\(event, privacy: .public)")
logger.debug("Received YouTube Player Event\n\(playerEvent, privacy: .public)")
}
// Send received event
// Send received player event
self.eventSubject.send(
.receivedEvent(event)
.receivedPlayerEvent(playerEvent)
)
// Cancel navigation action
return .cancel
Expand Down
116 changes: 10 additions & 106 deletions Sources/YouTubePlayer.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Combine
import Foundation
import OSLog

// MARK: - YouTubePlayer

Expand Down Expand Up @@ -226,115 +225,20 @@ extension YouTubePlayer: @preconcurrency Hashable {

}

// MARK: - Event Publisher

public extension YouTubePlayer {

/// A Publisher that emits a received ``YouTubePlayer/Event``
var eventPublisher: some Publisher<Event, Never> {
self.webView
.eventSubject
.compactMap(\.event)
.receive(on: DispatchQueue.main)
}

}

// MARK: - Evaluate

public extension YouTubePlayer {

/// Evaluates the JavaScript and converts its response.
/// - Parameters:
/// - javaScript: The JavaScript to evaluate.
/// - converter: The response converter.
func evaluate<Response>(
javaScript: JavaScript,
converter: JavaScriptEvaluationResponseConverter<Response>
) async throws(APIError) -> Response {
try await self.webView.evaluate(
javaScript: javaScript,
converter: converter
)
}

/// Evaluates the JavaScript.
/// - Parameter javaScript: The JavaScript to evaluate.
func evaluate(
javaScript: JavaScript
) async throws(APIError) {
try await self.evaluate(
javaScript: javaScript,
converter: .void
)
}

}

// MARK: - Reload

public extension YouTubePlayer {

/// Reloads the YouTube player.
func reload() async throws(Swift.Error) {
// Destroy the player and discard the error
try? await self.evaluate(
javaScript: .youTubePlayer(
functionName: "destroy"
)
)
// Reload
try self.webView.load()
// Await new ready or error state
for await state in self.stateSubject.dropFirst().values {
// Swithc on state
switch state {
case .ready:
// Success return out of function
return
case .error(let error):
// Throw error
throw error
default:
// Continue with next state
continue
}
}
}

}

// MARK: - Logger

public extension YouTubePlayer {

/// Returns a new logger instance if logging is enabled through `isLoggingEnabled`.
func logger() -> Logger? {
guard self.isLoggingEnabled else {
return nil
}
return .init(
subsystem: "YouTubePlayer",
category: .init(describing: self.id)
)
}

}

// MARK: - Handle WebView/JavaScript Event
// MARK: - Handle Event

private extension YouTubePlayer {

/// Handles a ``YouTubePlayerWebView.Event``
/// Handles a `YoutubePlayerWebView.Event`
/// - Parameter webViewEvent: The web view event to handle.
func handle(
webViewEvent: YouTubePlayerWebView.Event
) {
switch webViewEvent {
case .receivedEvent(let event):
// Handle event
case .receivedPlayerEvent(let playerEvent):
// Handle player event
self.handle(
event: event
playerEvent: playerEvent
)
case .didFailProvisionalNavigation(let error):
// Send did fail provisional navigation error
Expand All @@ -357,16 +261,16 @@ private extension YouTubePlayer {
/// Handles an incoming ``YouTubePlayer/Event``
/// - Parameter event: The event to handle.
func handle(
event: Event
playerEvent: Event
) {
// Switch on event name
switch event.name {
switch playerEvent.name {
case .iFrameApiFailedToLoad, .connectionIssue:
// Send error state
self.stateSubject.send(.error(.iFrameApiFailedToLoad))
case .error:
// Send error state
event
playerEvent
.data?
.value(as: Int.self)
.flatMap(YouTubePlayer.Error.init)
Expand All @@ -390,7 +294,7 @@ private extension YouTubePlayer {
}
case .stateChange:
// Verify YouTubePlayer PlaybackState is available
guard let playbackState = event
guard let playbackState = playerEvent
.data?
.value(as: Int.self)
.flatMap(PlaybackState.init(value:)) else {
Expand All @@ -400,7 +304,7 @@ private extension YouTubePlayer {
// Check if state is set to error
if playbackState != .unstarted, case .error = self.state {
// Handle onReady state
self.handle(event: .init(name: .ready))
self.handle(playerEvent: .init(name: .ready))
}
// Send PlaybackState
self.playbackStateSubject.send(playbackState)
Expand Down

0 comments on commit a592c43

Please sign in to comment.