Skip to content

Commit c736f8c

Browse files
committed
added events
1 parent aa2065c commit c736f8c

File tree

9 files changed

+166
-10
lines changed

9 files changed

+166
-10
lines changed

Sources/swiftui-loop-videoplayer/ExtVideoPlayer.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public struct ExtVideoPlayer: View{
2525
@State private var currentTime: Double = 0.0
2626

2727
/// The current state of the player event,
28-
@State private var playerEvent: PlayerEvent = .idle
28+
@State private var playerEvent: [PlayerEvent] = [.idle]
2929

3030
/// A publisher that emits the current playback time as a `Double`. It is initialized privately within the view.
3131
@State private var timePublisher = PassthroughSubject<Double, Never>()
@@ -114,7 +114,7 @@ public struct ExtVideoPlayer: View{
114114
.onReceive(timePublisher, perform: { time in
115115
currentTime = time
116116
})
117-
.onReceive(eventPublisher, perform: { event in
117+
.onReceive(eventPublisher.collect(.byTime(DispatchQueue.main, .seconds(2))), perform: { event in
118118
playerEvent = event
119119
})
120120
.preference(key: CurrentTimePreferenceKey.self, value: currentTime)

Sources/swiftui-loop-videoplayer/enum/PlayerEvent.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Foundation
9+
import AVFoundation
910

1011
/// An enumeration representing various events that can occur within a media player.
1112
@available(iOS 14.0, macOS 11.0, tvOS 14.0, *)
@@ -38,4 +39,46 @@ public enum PlayerEvent: Equatable {
3839
/// This state occurs when the player is currently playing video or audio content at the
3940
/// specified playback rate. This is the active state where media is being rendered to the user.
4041
case playing
42+
43+
/// Indicates that the player has switched to a new item.
44+
///
45+
/// This event is triggered when the player's `currentItem` changes to a new `AVPlayerItem`.
46+
/// - Parameter newItem: The new `AVPlayerItem` that the player has switched to.
47+
case currentItemChanged(newItem: AVPlayerItem?)
48+
49+
/// Indicates that the player has removed the current item.
50+
///
51+
/// This event is triggered when the player's `currentItem` is set to `nil`, meaning that there
52+
/// is no media item currently loaded in the player.
53+
case currentItemRemoved
54+
55+
/// Indicates that the player's volume has changed.
56+
///
57+
/// This event is triggered when the player's `volume` property is adjusted.
58+
/// - Parameter newVolume: The new volume level, ranging from 0.0 (muted) to 1.0 (full volume).
59+
case volumeChanged(newVolume: Float)
60+
61+
}
62+
63+
extension PlayerEvent: CustomStringConvertible {
64+
public var description: String {
65+
switch self {
66+
case .idle:
67+
return "Idle"
68+
case .seek(let success, _):
69+
return success ? "SeekSuccess" : "SeekFail"
70+
case .paused:
71+
return "Paused"
72+
case .waitingToPlayAtSpecifiedRate:
73+
return "Waiting"
74+
case .playing:
75+
return "Playing"
76+
case .currentItemChanged(_):
77+
return "ItemChanged"
78+
case .currentItemRemoved:
79+
return "ItemRemoved"
80+
case .volumeChanged(_):
81+
return "VolumeChanged"
82+
}
83+
}
4184
}

Sources/swiftui-loop-videoplayer/protocol/helpers/PlayerDelegateProtocol.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Foundation
9+
import AVFoundation
910

1011
/// Protocol to handle player-related errors.
1112
///
@@ -50,4 +51,25 @@ public protocol PlayerDelegateProtocol: AnyObject {
5051
/// This method is triggered when the player's `timeControlStatus` changes to `.playing`.
5152
@MainActor
5253
func didStartPlaying()
54+
55+
/// Called when the current media item in the player changes.
56+
///
57+
/// This method is triggered when the player's `currentItem` is updated to a new `AVPlayerItem`.
58+
/// - Parameter newItem: The new `AVPlayerItem` that the player has switched to, if any.
59+
@MainActor
60+
func currentItemDidChange(to newItem: AVPlayerItem?)
61+
62+
/// Called when the current media item is removed from the player.
63+
///
64+
/// This method is triggered when the player's `currentItem` is set to `nil`, indicating that there is no longer an active media item.
65+
@MainActor
66+
func currentItemWasRemoved()
67+
68+
/// Called when the volume level of the player changes.
69+
///
70+
/// This method is triggered when the player's `volume` property changes.
71+
/// - Parameter newVolume: The new volume level, expressed as a float between 0.0 (muted) and 1.0 (maximum volume).
72+
@MainActor
73+
func volumeDidChange(to newVolume: Float)
74+
5375
}

Sources/swiftui-loop-videoplayer/protocol/player/AbstractPlayer.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@ internal func cleanUp(
417417
playerLooper: inout AVPlayerLooper?,
418418
errorObserver: inout NSKeyValueObservation?,
419419
timeControlObserver: inout NSKeyValueObservation?,
420+
currentItemObserver: inout NSKeyValueObservation?,
421+
volumeObserver: inout NSKeyValueObservation?,
420422
statusObserver: inout NSKeyValueObservation?,
421423
timeObserver: inout Any?
422424
) {
@@ -427,6 +429,12 @@ internal func cleanUp(
427429
timeControlObserver?.invalidate()
428430
timeControlObserver = nil
429431

432+
currentItemObserver?.invalidate()
433+
currentItemObserver = nil
434+
435+
volumeObserver?.invalidate()
436+
volumeObserver = nil
437+
430438
statusObserver?.invalidate()
431439
statusObserver = nil
432440

Sources/swiftui-loop-videoplayer/protocol/player/LoopingPlayerProtocol.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ public protocol LoopingPlayerProtocol: AbstractPlayer, LayerMakerProtocol{
3737
/// An optional observer for monitoring changes to the player's `timeControlStatus` property.
3838
var timeControlObserver: NSKeyValueObservation? { get set }
3939

40+
/// An optional observer for monitoring changes to the player's `currentItem` property.
41+
var currentItemObserver: NSKeyValueObservation? { get set }
42+
43+
/// An optional observer for monitoring changes to the player's `volume` property.
44+
///
45+
/// This property holds an instance of `NSKeyValueObservation`, which observes the `volume`
46+
/// of an `AVPlayer`.
47+
var volumeObserver: NSKeyValueObservation? { get set }
48+
4049
/// Declare a variable to hold the time observer token outside the if statement
4150
var timeObserver: Any? { get set }
4251

@@ -226,6 +235,21 @@ internal extension LoopingPlayerProtocol {
226235
break
227236
}
228237
}
238+
239+
currentItemObserver = player.observe(\.currentItem, options: [.new, .old]) { [weak self] player, change in
240+
// Detecting when the current item is changed
241+
if let newItem = change.newValue as? AVPlayerItem {
242+
self?.delegate?.currentItemDidChange(to: newItem)
243+
} else if change.newValue == nil {
244+
self?.delegate?.currentItemWasRemoved()
245+
}
246+
}
247+
248+
volumeObserver = player.observe(\.volume, options: [.new, .old]) { [weak self] player, change in
249+
if let newVolume = change.newValue as? Float {
250+
self?.delegate?.volumeDidChange(to: newVolume)
251+
}
252+
}
229253
}
230254

231255
/// Removes observers for handling errors.

Sources/swiftui-loop-videoplayer/view/helpers/PlayerCoordinator.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import SwiftUI
99
import Combine
10+
import AVFoundation
1011

1112
@MainActor
1213
internal class PlayerCoordinator: NSObject, PlayerDelegateProtocol {
@@ -96,4 +97,30 @@ internal class PlayerCoordinator: NSObject, PlayerDelegateProtocol {
9697
func didStartPlaying(){
9798
eventPublisher.send(.playing)
9899
}
100+
101+
/// Called when the current media item in the player changes.
102+
///
103+
/// This method is triggered when the player's `currentItem` is updated to a new `AVPlayerItem`.
104+
/// - Parameter newItem: The new `AVPlayerItem` that the player has switched to, if any.
105+
@MainActor
106+
func currentItemDidChange(to newItem: AVPlayerItem?){
107+
eventPublisher.send(.currentItemChanged(newItem: newItem))
108+
}
109+
110+
/// Called when the current media item is removed from the player.
111+
///
112+
/// This method is triggered when the player's `currentItem` is set to `nil`, indicating that there is no longer an active media item.
113+
@MainActor
114+
func currentItemWasRemoved(){
115+
eventPublisher.send(.currentItemRemoved)
116+
}
117+
118+
/// Called when the volume level of the player changes.
119+
///
120+
/// This method is triggered when the player's `volume` property changes.
121+
/// - Parameter newVolume: The new volume level, expressed as a float between 0.0 (muted) and 1.0 (maximum volume).
122+
@MainActor
123+
func volumeDidChange(to newVolume: Float){
124+
eventPublisher.send(.volumeChanged(newVolume: newVolume))
125+
}
99126
}

Sources/swiftui-loop-videoplayer/view/modifier/OnPlayerEventChangeModifier.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
import SwiftUI
99

1010
internal struct PlayerEventPreferenceKey: PreferenceKey {
11-
public static var defaultValue: PlayerEvent = .idle
12-
public static func reduce(value: inout PlayerEvent, nextValue: () -> PlayerEvent) {
11+
public static var defaultValue: [PlayerEvent] = []
12+
public static func reduce(value: inout [PlayerEvent], nextValue: () -> [PlayerEvent]) {
1313
value = nextValue()
1414
}
1515
}
1616

1717
internal struct OnPlayerEventChangeModifier: ViewModifier {
18-
var onPlayerEventChange: (PlayerEvent) -> Void
18+
var onPlayerEventChange: ([PlayerEvent]) -> Void
1919

2020
func body(content: Content) -> some View {
2121
content
@@ -26,7 +26,7 @@ internal struct OnPlayerEventChangeModifier: ViewModifier {
2626
}
2727

2828
public extension View {
29-
func onPlayerEventChange(perform action: @escaping (PlayerEvent) -> Void) -> some View {
29+
func onPlayerEventChange(perform action: @escaping ([PlayerEvent]) -> Void) -> some View {
3030
self.modifier(OnPlayerEventChangeModifier(onPlayerEventChange: action))
3131
}
3232
}

Sources/swiftui-loop-videoplayer/view/player/ios/LoopingPlayerUIView.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ class LoopingPlayerUIView: UIView, LoopingPlayerProtocol {
4848
/// An optional observer for monitoring changes to the player's `timeControlStatus` property.
4949
internal var timeControlObserver: NSKeyValueObservation?
5050

51+
/// An optional observer for monitoring changes to the player's `currentItem` property.
52+
internal var currentItemObserver: NSKeyValueObservation?
53+
54+
/// An optional observer for monitoring changes to the player's `volume` property.
55+
///
56+
/// This property holds an instance of `NSKeyValueObservation`, which observes the `volume`
57+
/// of an `AVPlayer`.
58+
internal var volumeObserver: NSKeyValueObservation?
59+
5160
/// Observes the status property of the new player item.
5261
internal var statusObserver: NSKeyValueObservation?
5362

@@ -83,8 +92,15 @@ class LoopingPlayerUIView: UIView, LoopingPlayerProtocol {
8392
/// This method invalidates the status and error observers to prevent memory leaks,
8493
/// pauses the player, and clears out player-related references to assist in clean deinitialization.
8594
deinit {
86-
cleanUp(player: &player, playerLooper: &playerLooper, errorObserver: &errorObserver,
87-
timeControlObserver : &timeControlObserver, statusObserver: &statusObserver, timeObserver: &timeObserver)
95+
cleanUp(
96+
player: &player,
97+
playerLooper: &playerLooper,
98+
errorObserver: &errorObserver,
99+
timeControlObserver : &timeControlObserver,
100+
currentItemObserver: &currentItemObserver,
101+
volumeObserver: &volumeObserver,
102+
statusObserver: &statusObserver,
103+
timeObserver: &timeObserver)
88104
}
89105
}
90106
#endif

Sources/swiftui-loop-videoplayer/view/player/mac/LoopingPlayerNSView.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ class LoopingPlayerNSView: NSView, LoopingPlayerProtocol {
5050
/// An optional observer for monitoring changes to the player's `timeControlStatus` property.
5151
internal var timeControlObserver: NSKeyValueObservation?
5252

53+
/// An optional observer for monitoring changes to the player's `currentItem` property.
54+
internal var currentItemObserver: NSKeyValueObservation?
55+
56+
/// An optional observer for monitoring changes to the player's `volume` property.
57+
///
58+
/// This property holds an instance of `NSKeyValueObservation`, which observes the `volume`
59+
/// of an `AVPlayer`.
60+
internal var volumeObserver: NSKeyValueObservation?
61+
5362
/// Observes the status property of the new player item.
5463
internal var statusObserver: NSKeyValueObservation?
5564

@@ -85,8 +94,15 @@ class LoopingPlayerNSView: NSView, LoopingPlayerProtocol {
8594
/// This method invalidates the status and error observers to prevent memory leaks,
8695
/// pauses the player, and clears out player-related references to assist in clean deinitialization.
8796
deinit {
88-
cleanUp(player: &player, playerLooper: &playerLooper, errorObserver: &errorObserver,
89-
timeControlObserver : &timeControlObserver, statusObserver: &statusObserver, timeObserver: &timeObserver)
97+
cleanUp(
98+
player: &player,
99+
playerLooper: &playerLooper,
100+
errorObserver: &errorObserver,
101+
timeControlObserver : &timeControlObserver,
102+
currentItemObserver: &currentItemObserver,
103+
volumeObserver: &volumeObserver,
104+
statusObserver: &statusObserver,
105+
timeObserver: &timeObserver)
90106
}
91107
}
92108
#endif

0 commit comments

Comments
 (0)