From 80173611db4ad0cc5abc345307752a4bc5ac02c6 Mon Sep 17 00:00:00 2001 From: Igor Date: Thu, 8 Aug 2024 11:05:48 +0200 Subject: [PATCH] code optimization --- README.md | 14 +++--- .../LoopPlayerView.swift | 26 +++++++--- Sources/swiftui-loop-videoplayer/fn/fn+.swift | 2 +- .../view/LoopPlayerViewProtocol.swift | 6 +-- .../protocol/view/LoopingPlayerProtocol.swift | 42 ++++++++++++++++- .../{Settings.swift => VideoSettings.swift} | 2 +- .../loop/main/LoopPlayerMultiPlatform.swift | 47 ++++++++++++++----- 7 files changed, 109 insertions(+), 30 deletions(-) rename Sources/swiftui-loop-videoplayer/utils/{Settings.swift => VideoSettings.swift} (97%) diff --git a/README.md b/README.md index 6452d69..12df739 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,10 @@ Please note that using videos from URLs requires ensuring that you have the righ | Property/Method | Type | Description | |---------------------------------------|-------------------------------|------------------------------------------------------------------------| -| `settings` | `Settings` | A struct containing configuration settings for the video player. | +| `settings` | `VideoSettings` | A struct containing configuration settings for the video player. | | `command` | `Binding` | A binding to control playback actions (play, pause, or seek). | | `init(fileName:ext:gravity:`
`eColor:eFontSize:command:)` | Constructor | Initializes the player with specific video parameters and playback command binding. | -| `init(settings: () -> Settings, command:)` | Constructor | Initializes the player with a declarative settings block and playback command binding. | +| `init(settings: () -> VideoSettings, command:)` | Constructor | Initializes the player with a declarative settings block and playback command binding. | ### Playback Commands @@ -71,7 +71,7 @@ or in a declarative way ```swift LoopPlayerView{ - Settings{ + VideoSettings{ SourceName("swipe") Ext("mp8") // Set default extension here If not provided then mp4 is default Gravity(.resizeAspectFill) @@ -85,7 +85,7 @@ or in a declarative way ```swift LoopPlayerView{ - Settings{ + VideoSettings{ SourceName("swipe") Gravity(.resizeAspectFill) EFontSize(27) @@ -95,7 +95,7 @@ or in a declarative way ```swift LoopPlayerView{ - Settings{ + VideoSettings{ SourceName('https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8') ErrorGroup{ EFontSize(27) @@ -128,7 +128,7 @@ The package now supports using remote video URLs, allowing you to stream videos ```swift LoopPlayerView{ - Settings{ + VideoSettings{ SourceName('https://example.com/video') Gravity(.resizeAspectFill) // Video content fit ErrorGroup{ @@ -160,7 +160,7 @@ struct VideoView: View { var body: some View { LoopPlayerView( { - Settings { + VideoSettings { SourceName("swipe") } }, diff --git a/Sources/swiftui-loop-videoplayer/LoopPlayerView.swift b/Sources/swiftui-loop-videoplayer/LoopPlayerView.swift index 1af2347..4a35f59 100644 --- a/Sources/swiftui-loop-videoplayer/LoopPlayerView.swift +++ b/Sources/swiftui-loop-videoplayer/LoopPlayerView.swift @@ -15,7 +15,7 @@ import AVKit public struct LoopPlayerView: View { /// Set of settings for video the player - public let settings: Settings + @Binding public var settings: VideoSettings /// Binding to a playback command that controls playback actions @Binding public var command: PlaybackCommand @@ -44,7 +44,8 @@ public struct LoopPlayerView: View { ) { self._command = command - settings = Settings { + _settings = .constant( + VideoSettings { SourceName(fileName) Ext(ext) Gravity(gravity) @@ -53,6 +54,7 @@ public struct LoopPlayerView: View { EFontSize(eFontSize) } } + ) } /// Player initializer in a declarative way @@ -60,18 +62,30 @@ public struct LoopPlayerView: View { /// - settings: Set of settings /// - command: A binding to control playback actions public init( - _ settings: () -> Settings, + _ settings: () -> VideoSettings, command: Binding = .constant(.play) ) { + + self._command = command + _settings = .constant(settings()) + } + + /// Player initializer in a declarative way + /// - Parameters: + /// - settings: A binding to the set of settings for the video player + /// - command: A binding to control playback actions + public init( + settings: Binding, + command: Binding = .constant(.play) + ) { + self._settings = settings self._command = command - self.settings = settings() } // MARK: - API public var body: some View { - LoopPlayerMultiPlatform(settings: settings, command: $command) + LoopPlayerMultiPlatform(settings: $settings, command: $command) .frame(maxWidth: .infinity, maxHeight: .infinity) - .id(videoId) } } diff --git a/Sources/swiftui-loop-videoplayer/fn/fn+.swift b/Sources/swiftui-loop-videoplayer/fn/fn+.swift index d63d807..9e2c6ed 100644 --- a/Sources/swiftui-loop-videoplayer/fn/fn+.swift +++ b/Sources/swiftui-loop-videoplayer/fn/fn+.swift @@ -73,7 +73,7 @@ func cleanUp(player: inout AVQueuePlayer?, playerLooper: inout AVPlayerLooper?, /// - settings: The settings for the video player. /// - asset: The asset for the video player. /// - Returns: The detected error or nil if no error. -func detectError(settings: Settings, asset: AVURLAsset?) -> VPErrors? { +func detectError(settings: VideoSettings, asset: AVURLAsset?) -> VPErrors? { if !settings.areUnique { return .settingsNotUnique } else if asset == nil { diff --git a/Sources/swiftui-loop-videoplayer/protocol/view/LoopPlayerViewProtocol.swift b/Sources/swiftui-loop-videoplayer/protocol/view/LoopPlayerViewProtocol.swift index 6a62236..00fef03 100644 --- a/Sources/swiftui-loop-videoplayer/protocol/view/LoopPlayerViewProtocol.swift +++ b/Sources/swiftui-loop-videoplayer/protocol/view/LoopPlayerViewProtocol.swift @@ -33,16 +33,16 @@ public protocol LoopPlayerViewProtocol { #endif /// Settings for configuring the video player. - var settings: Settings { get } + var settings: VideoSettings { get set } /// Initializes a new instance with the provided settings and playback command. /// /// - Parameters: - /// - settings: An instance of `Settings` containing configuration details. + /// - settings: A binding to a `VideoSettings` containing configuration details. /// - command: A binding to a `PlaybackCommand` that controls playback actions. /// /// This initializer sets up the necessary configuration and command bindings for playback functionality. - init(settings: Settings, command: Binding) + init(settings: Binding, command: Binding) } diff --git a/Sources/swiftui-loop-videoplayer/protocol/view/LoopingPlayerProtocol.swift b/Sources/swiftui-loop-videoplayer/protocol/view/LoopingPlayerProtocol.swift index 3cc93d1..2aee701 100644 --- a/Sources/swiftui-loop-videoplayer/protocol/view/LoopingPlayerProtocol.swift +++ b/Sources/swiftui-loop-videoplayer/protocol/view/LoopingPlayerProtocol.swift @@ -70,7 +70,47 @@ public protocol LoopingPlayerProtocol: AbstractPlayer { } extension LoopingPlayerProtocol { - + + /// The current asset being played, if available. + /// + /// This computed property checks the current item of the player. + /// If the current item exists and its asset can be cast to AVURLAsset, + var currentAsset : AVURLAsset?{ + if let currentItem = player?.currentItem { + return currentItem.asset as? AVURLAsset + } + return nil + } + + /// Updates the player to play a new asset and handles the playback state. + /// + /// This method pauses the player if it was previously playing, + /// replaces the current player item with a new item created from the provided asset, + /// and seeks to the start of the new item. It resumes playing if the player was playing before the update. + /// + /// - Parameters: + /// - asset: The AVURLAsset to load into the player. + func update(asset: AVURLAsset){ + // Optionally, check if the player is currently playing + let wasPlaying = player?.rate != 0 + + // Pause the player if it was playing + if wasPlaying { + player?.pause() + } + + // Replace the current item with a new item created from the asset + let newItem = AVPlayerItem(asset: asset) + player?.replaceCurrentItem(with: newItem) + + // Seek to the beginning of the item if you want to start from the start + player?.seek(to: .zero, completionHandler: { _ in + // Resume playing if the player was playing before + if wasPlaying { + self.player?.play() + } + }) + } /// Sets up the player components using the provided asset and video gravity. /// diff --git a/Sources/swiftui-loop-videoplayer/utils/Settings.swift b/Sources/swiftui-loop-videoplayer/utils/VideoSettings.swift similarity index 97% rename from Sources/swiftui-loop-videoplayer/utils/Settings.swift rename to Sources/swiftui-loop-videoplayer/utils/VideoSettings.swift index 72910ba..ede5fcd 100644 --- a/Sources/swiftui-loop-videoplayer/utils/Settings.swift +++ b/Sources/swiftui-loop-videoplayer/utils/VideoSettings.swift @@ -9,7 +9,7 @@ import SwiftUI import AVKit @available(iOS 14.0, macOS 11.0, tvOS 14.0, *) -public struct Settings: Equatable{ +public struct VideoSettings: Equatable{ // MARK: - Public properties diff --git a/Sources/swiftui-loop-videoplayer/view/loop/main/LoopPlayerMultiPlatform.swift b/Sources/swiftui-loop-videoplayer/view/loop/main/LoopPlayerMultiPlatform.swift index 6fbbdd9..6282e93 100644 --- a/Sources/swiftui-loop-videoplayer/view/loop/main/LoopPlayerMultiPlatform.swift +++ b/Sources/swiftui-loop-videoplayer/view/loop/main/LoopPlayerMultiPlatform.swift @@ -40,13 +40,14 @@ struct LoopPlayerMultiPlatform: LoopPlayerViewProtocol { @Binding public var command : PlaybackCommand /// Settings for the player view - public let settings: Settings - - /// The video asset to be played. - private let asset: AVURLAsset? - + @Binding public var settings: VideoSettings + /// State to store any error that occurs @State private var error: VPErrors? + + var asset : AVURLAsset?{ + assetForName(name: settings.name, ext: settings.ext) + } /// Initializes a new instance with the provided settings and playback command. /// @@ -55,11 +56,12 @@ struct LoopPlayerMultiPlatform: LoopPlayerViewProtocol { /// - command: A binding to a `PlaybackCommand` that controls playback actions. /// /// This initializer sets up the necessary configuration and command bindings for playback functionality. - init(settings: Settings, command: Binding) { - self.settings = settings + init(settings: Binding, command: Binding) { + self._settings = settings self._command = command - self.asset = assetForName(name: settings.name, ext: settings.ext) - self._error = State(initialValue: detectError(settings: settings, asset: self.asset)) + let settings = settings.wrappedValue + let asset = assetForName(name: settings.name, ext: settings.ext) + self._error = State(initialValue: detectError(settings: settings, asset: asset)) } @@ -109,7 +111,7 @@ extension LoopPlayerMultiPlatform: NSViewRepresentable{ /// - Returns: A fully configured NSView containing both the media player and potentially an error message display. @MainActor func makeNSView(context: Context) -> NSView { let container = NSView() - + if let player: PlayerView = makePlayerView( container, asset: asset){ @@ -128,9 +130,32 @@ extension LoopPlayerMultiPlatform: NSViewRepresentable{ @MainActor func updateNSView(_ nsView: NSView, context: Context) { nsView.subviews.filter { $0 is ErrorView }.forEach { $0.removeFromSuperview() } - nsView.subviews.compactMap{ $0 as? LoopingPlayerProtocol }.forEach { $0.setCommand(command) } + nsView.subviews.compactMap{ $0 as? LoopingPlayerProtocol }.forEach { + if let asset = getAssetIfChanged(settings: settings, asset: $0.currentAsset){ + $0.update(asset: asset) + }else{ + $0.setCommand(command) + } + } updateView(nsView, error: error) } } #endif + +fileprivate func getAssetIfChanged(settings: VideoSettings, asset: AVURLAsset?) -> AVURLAsset?{ + let a = assetForName(name: settings.name, ext: settings.ext) + + guard asset != nil else{ + return a + } + + if let newUrl = a?.url, let oldUrl = asset?.url, newUrl != oldUrl{ + return a + } + + return nil +} + + +