Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions ios/core/Sources/Player/HeadlessPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ public protocol HeadlessPlayer {
associatedtype RegistryType: PlayerRegistry
/// Registry for Assets
var assetRegistry: RegistryType { get }

var pluginManager: PluginManager { get }

/// The current state of Player
var state: BaseFlowState? { get }
/// A reference to the core player in the JSContext
Expand Down Expand Up @@ -293,18 +296,6 @@ public extension HeadlessPlayer {
logger.e("JavaScriptCore Exception: \(String(describing: value)) \(moreInfo)")
}
}

func findPlugin<Plugin: WithSymbol>(_ plugin: Plugin.Type) -> JSValue? {
return jsPlayerReference?
.invokeMethod("findPlugin", withArguments: [
jsPlayerReference?.context.getSymbol(plugin.symbol) as Any
])
}

func applyTo<Plugin: WithSymbol>(_ plugin: Plugin.Type, apply: @escaping (JSValue) -> Void) {
guard let plugin = findPlugin(plugin) else { return }
apply(plugin)
}
}

public protocol WithSymbol {
Expand Down
54 changes: 54 additions & 0 deletions ios/core/Sources/PluginManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// PluginManager.swift
// PlayerUI
//
// Created by Zhao Xia Wu on 2025-02-25.
//

import Foundation
import SwiftHooks

public class PluginManager {
private var plugins: [NativePlugin] = []

public var hooks = PluginManagerHooks()

public init() {
setupHooks()
}

private func setupHooks() {
// Registering the plugin
hooks.registerPlugin.tap(name: "RegisterPluginAppend") { plugin in
if !self.plugins.contains(where: { $0.pluginName == plugin.pluginName }) {
self.plugins.append(plugin)
}
}

// Finding the plugin
hooks.findPlugin.tap(name: "FindPlugin") { pluginType in
guard let match = self.plugins.first(where: { type(of: $0) == pluginType }) else {
return .skip
}

Check warning on line 32 in ios/core/Sources/PluginManager.swift

View check run for this annotation

Codecov / codecov/patch

ios/core/Sources/PluginManager.swift#L31-L32

Added lines #L31 - L32 were not covered by tests
return .bail(match)
}
}

// Method to add a plugin to the Manager where pluginNames are unique
public func registerPlugin(_ plugin: NativePlugin) {
self.hooks.registerPlugin.call(plugin)
}

// Method to retrieve a plugin by type
public func findPlugin<T: NativePlugin>(ofType type: T.Type) -> T? {
return self.hooks.findPlugin.call(type) as? T
}
}

public struct PluginManagerHooks {
// Hook for registering a new plugin
public let registerPlugin = SyncHook<NativePlugin>()

// Hook for finding plugins
public let findPlugin = SyncBailHook<NativePlugin.Type, NativePlugin?>()
}
14 changes: 13 additions & 1 deletion ios/swiftui/Sources/SwiftUIPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public struct SwiftUIPlayer: View, HeadlessPlayer {
fileprivate(set) var hooks: SwiftUIPlayerHooks?
fileprivate let registry = SwiftUIRegistry()

fileprivate let pluginManager = PluginManager()

@Published fileprivate var result: Result<CompletedState, PlayerError>?

/// Returns true iff there is a non-nil player.
Expand Down Expand Up @@ -76,7 +78,15 @@ public struct SwiftUIPlayer: View, HeadlessPlayer {
self.hooks = hooks
DispatchQueue.main.async { self.result = nil }

for plugin in allPlugins { plugin.apply(player: player) }
// To ensure plugin.apply gets called even if plugin were to get registered after player.start
pluginManager.hooks.registerPlugin.tap(name: "RegisterPluginApply") { plugin in
plugin.apply(player: player)
}

for plugin in allPlugins {
pluginManager.registerPlugin(plugin)
}

registry.partialMatchRegistry = partialMatchPlugin

hooks.viewController.tap { [weak self] controller in
Expand Down Expand Up @@ -178,6 +188,8 @@ public struct SwiftUIPlayer: View, HeadlessPlayer {
/// The registry for registering assets to be used for rendering
public var assetRegistry: SwiftUIRegistry { context.registry }

public var pluginManager: PluginManager { context.pluginManager }

// For ViewInspector testing
internal let inspection = Inspection<Self>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ open class HeadlessPlayerImpl: HeadlessPlayer {
public var hooks: HeadlessHooks?
public var logger = TapableLogger()

public var pluginManager = PluginManager()

public var jsPlayerReference: JSValue?

let match = PartialMatchFingerprintPlugin()
Expand Down
1 change: 1 addition & 0 deletions ios/test-utils-core/Sources/utilities/TestPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class TestPlayer<WrapperType: AssetContainer, RegistryType: BaseAssetRegi
public var hooks: TestHooks?

public var logger = TapableLogger()
public var pluginManager = PluginManager()

public let assetRegistry: RegistryType

Expand Down
13 changes: 11 additions & 2 deletions plugins/metrics/swiftui/Sources/MetricsPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ public class RequestTimePlugin: NativePlugin {

public func apply<P>(player: P) where P: HeadlessPlayer {
requestTimeWebPlugin.context = player.jsPlayerReference?.context
player.applyTo(MetricsPlugin.self) { [weak self] plugin in
self?.requestTimeWebPlugin.pluginRef?.invokeMethod("apply", withArguments: [plugin])

guard let metricsPlugin = player.pluginManager.findPlugin(ofType: MetricsPlugin.self) else {
return
}

self.requestTimeWebPlugin.apply(to: metricsPlugin)
}
}

Expand All @@ -47,6 +50,12 @@ class RequestTimeWebPlugin: JSBasePlugin {
return [JSValue(object: handler, in: context) as Any]
}

fileprivate func apply(to metricsPlugin: MetricsPlugin) {
guard let context = metricsPlugin.context else { return }
self.setup(context: context)
self.pluginRef?.invokeMethod("apply", withArguments: [metricsPlugin.pluginRef])
}

override open func getUrlForFile(fileName: String) -> URL? {
#if SWIFT_PACKAGE
ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module)
Expand Down