@capgo/capacitor-video-player 8.2.4 → 8.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,7 +46,7 @@ import org.json.JSONObject;
46
46
  )
47
47
  public class VideoPlayerPlugin extends Plugin {
48
48
 
49
- private final String pluginVersion = "8.2.4";
49
+ private final String pluginVersion = "8.2.7";
50
50
 
51
51
  // Permission alias constants
52
52
  private static final String PERMISSION_DENIED_ERROR = "Unable to access media videos, user denied permission request";
@@ -0,0 +1,227 @@
1
+ import AVKit
2
+ import UIKit
3
+
4
+ /// Mirrors AVKit playback-control visibility for custom overlay views.
5
+ ///
6
+ /// AVPlayerViewController does not expose control visibility publicly. This observer
7
+ /// locates the controls container using only the public view hierarchy and watches
8
+ /// `hidden` / `alpha` so overlay controls can follow native chrome.
9
+ final class AVPlayerControlsVisibilityObserver: NSObject {
10
+ private weak var playerViewController: AVPlayerViewController?
11
+ private weak var monitoredView: UIView?
12
+ private var onVisibilityChange: ((Bool) -> Void)?
13
+ private var retryWorkItem: DispatchWorkItem?
14
+ private var reanchorWorkItem: DispatchWorkItem?
15
+ private var hiddenObservation: NSKeyValueObservation?
16
+ private var alphaObservation: NSKeyValueObservation?
17
+ private var lastReportedVisible: Bool?
18
+ private var remainingInstallAttempts = 30
19
+ private var isActive = false
20
+
21
+ private static let reanchorInterval: TimeInterval = 0.25
22
+
23
+ deinit {
24
+ stop()
25
+ }
26
+
27
+ func start(
28
+ observing playerViewController: AVPlayerViewController,
29
+ onChange: @escaping (Bool) -> Void
30
+ ) {
31
+ stop()
32
+ isActive = true
33
+ self.playerViewController = playerViewController
34
+ self.onVisibilityChange = onChange
35
+ remainingInstallAttempts = 30
36
+ installObserverIfPossible()
37
+ scheduleReanchorCheck()
38
+ }
39
+
40
+ func stop() {
41
+ isActive = false
42
+ retryWorkItem?.cancel()
43
+ retryWorkItem = nil
44
+ reanchorWorkItem?.cancel()
45
+ reanchorWorkItem = nil
46
+ removeMonitoredViewObserver()
47
+ playerViewController = nil
48
+ onVisibilityChange = nil
49
+ lastReportedVisible = nil
50
+ }
51
+
52
+ static func controlsContainer(in playerView: UIView) -> UIView? {
53
+ guard let container = playerView.subviews.first else {
54
+ return nil
55
+ }
56
+ return container.subviews.last
57
+ }
58
+
59
+ static func areControlsVisible(in controlsView: UIView?) -> Bool {
60
+ guard let controlsView else {
61
+ return true
62
+ }
63
+ return !controlsView.isHidden && controlsView.alpha > 0.05
64
+ }
65
+
66
+ static func shouldReanchor(
67
+ monitoredView: UIView?,
68
+ currentControlsView: UIView?
69
+ ) -> Bool {
70
+ guard let monitoredView else {
71
+ return currentControlsView != nil
72
+ }
73
+ guard let currentControlsView else {
74
+ return true
75
+ }
76
+ if monitoredView !== currentControlsView {
77
+ return true
78
+ }
79
+ return monitoredView.superview == nil && monitoredView.window == nil
80
+ }
81
+
82
+ private func installObserverIfPossible() {
83
+ guard isActive else {
84
+ return
85
+ }
86
+
87
+ guard let playerView = playerViewController?.view else {
88
+ scheduleRetry()
89
+ return
90
+ }
91
+
92
+ guard let controlsView = Self.controlsContainer(in: playerView) else {
93
+ scheduleRetry()
94
+ return
95
+ }
96
+
97
+ if monitoredView === controlsView {
98
+ reportVisibilityIfNeeded(for: controlsView)
99
+ return
100
+ }
101
+
102
+ removeMonitoredViewObserver()
103
+ attachObserver(to: controlsView)
104
+ }
105
+
106
+ private func attachObserver(to controlsView: UIView) {
107
+ monitoredView = controlsView
108
+ hiddenObservation = controlsView.observe(\.isHidden, options: [.new]) { [weak self] view, _ in
109
+ self?.reportVisibilityIfNeeded(for: view)
110
+ }
111
+ alphaObservation = controlsView.observe(\.alpha, options: [.new]) { [weak self] view, _ in
112
+ self?.reportVisibilityIfNeeded(for: view)
113
+ }
114
+ reportVisibilityIfNeeded(for: controlsView)
115
+ }
116
+
117
+ private func reanchorIfNeeded() {
118
+ guard isActive else {
119
+ return
120
+ }
121
+
122
+ guard let playerView = playerViewController?.view else {
123
+ if monitoredView != nil {
124
+ removeMonitoredViewObserver()
125
+ }
126
+ scheduleRetry()
127
+ return
128
+ }
129
+
130
+ let currentControlsView = Self.controlsContainer(in: playerView)
131
+ guard Self.shouldReanchor(
132
+ monitoredView: monitoredView,
133
+ currentControlsView: currentControlsView
134
+ ) else {
135
+ if let monitoredView {
136
+ reportVisibilityIfNeeded(for: monitoredView)
137
+ }
138
+ return
139
+ }
140
+
141
+ removeMonitoredViewObserver()
142
+ if let currentControlsView {
143
+ remainingInstallAttempts = 30
144
+ attachObserver(to: currentControlsView)
145
+ } else {
146
+ scheduleRetry()
147
+ }
148
+ }
149
+
150
+ private func scheduleReanchorCheck() {
151
+ reanchorWorkItem?.cancel()
152
+ guard isActive else {
153
+ return
154
+ }
155
+
156
+ let workItem = DispatchWorkItem { [weak self] in
157
+ guard let self, self.isActive else {
158
+ return
159
+ }
160
+ self.reanchorIfNeeded()
161
+ self.scheduleReanchorCheck()
162
+ }
163
+ reanchorWorkItem = workItem
164
+ DispatchQueue.main.asyncAfter(deadline: .now() + Self.reanchorInterval, execute: workItem)
165
+ }
166
+
167
+ private func scheduleRetry() {
168
+ guard isActive else {
169
+ return
170
+ }
171
+
172
+ retryWorkItem?.cancel()
173
+ guard remainingInstallAttempts > 0 else {
174
+ lastReportedVisible = true
175
+ onVisibilityChange?(true)
176
+ return
177
+ }
178
+ remainingInstallAttempts -= 1
179
+
180
+ let workItem = DispatchWorkItem { [weak self] in
181
+ guard let self, self.isActive else {
182
+ return
183
+ }
184
+ self.installObserverIfPossible()
185
+ }
186
+ retryWorkItem = workItem
187
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: workItem)
188
+ }
189
+
190
+ private func removeMonitoredViewObserver() {
191
+ hiddenObservation = nil
192
+ alphaObservation = nil
193
+ monitoredView = nil
194
+ }
195
+
196
+ private func reportVisibilityIfNeeded(for controlsView: UIView) {
197
+ let visible = Self.areControlsVisible(in: controlsView)
198
+ guard lastReportedVisible != visible else {
199
+ return
200
+ }
201
+ lastReportedVisible = visible
202
+ onVisibilityChange?(visible)
203
+ }
204
+ }
205
+
206
+ enum VideoPlayerCastSeekSync {
207
+ static let minimumForwardDelta: Double = 0.25
208
+
209
+ static func shouldForwardLocalSeek(
210
+ localTime: Double,
211
+ lastForwardedTime: Double?
212
+ ) -> Bool {
213
+ guard localTime.isFinite else {
214
+ return false
215
+ }
216
+ guard let lastForwardedTime else {
217
+ return false
218
+ }
219
+ return abs(localTime - lastForwardedTime) >= minimumForwardDelta
220
+ }
221
+ }
222
+
223
+ enum VideoPlayerCastOverlayLayout {
224
+ static func overlayView(for playerViewController: AVPlayerViewController) -> UIView? {
225
+ playerViewController.contentOverlayView
226
+ }
227
+ }
@@ -0,0 +1,20 @@
1
+ import AVKit
2
+ import UIKit
3
+
4
+ final class DismissObservingPlayerViewController: AVPlayerViewController {
5
+ var onDismiss: (() -> Void)?
6
+ var shouldReportDismiss: (() -> Bool)?
7
+ weak var adaptivePresentationDelegate: UIAdaptivePresentationControllerDelegate?
8
+
9
+ override func viewWillAppear(_ animated: Bool) {
10
+ super.viewWillAppear(animated)
11
+ presentationController?.delegate = adaptivePresentationDelegate
12
+ }
13
+
14
+ override func viewDidDisappear(_ animated: Bool) {
15
+ super.viewDidDisappear(animated)
16
+ guard isBeingDismissed else { return }
17
+ guard shouldReportDismiss?() ?? true else { return }
18
+ onDismiss?()
19
+ }
20
+ }
@@ -0,0 +1,11 @@
1
+ import Foundation
2
+
3
+ struct ExitEmissionGuard {
4
+ private(set) var didEmit = false
5
+
6
+ mutating func emitIfNeeded(_ action: () -> Void) {
7
+ guard !didEmit else { return }
8
+ didEmit = true
9
+ action()
10
+ }
11
+ }