@granite-js/video 1.0.0

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/GraniteVideo.podspec +72 -0
  3. package/android/README.md +232 -0
  4. package/android/build.gradle +117 -0
  5. package/android/gradle.properties +8 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/run/granite/video/GraniteVideoModule.kt +70 -0
  8. package/android/src/main/java/run/granite/video/GraniteVideoPackage.kt +43 -0
  9. package/android/src/main/java/run/granite/video/GraniteVideoView.kt +384 -0
  10. package/android/src/main/java/run/granite/video/GraniteVideoViewManager.kt +318 -0
  11. package/android/src/main/java/run/granite/video/event/GraniteVideoEvents.kt +273 -0
  12. package/android/src/main/java/run/granite/video/event/VideoEventDispatcher.kt +66 -0
  13. package/android/src/main/java/run/granite/video/event/VideoEventListenerAdapter.kt +157 -0
  14. package/android/src/main/java/run/granite/video/provider/GraniteVideoProvider.kt +346 -0
  15. package/android/src/media3/AndroidManifest.xml +9 -0
  16. package/android/src/media3/java/run/granite/video/provider/media3/ExoPlayerProvider.kt +386 -0
  17. package/android/src/media3/java/run/granite/video/provider/media3/Media3ContentProvider.kt +29 -0
  18. package/android/src/media3/java/run/granite/video/provider/media3/Media3Initializer.kt +25 -0
  19. package/android/src/media3/java/run/granite/video/provider/media3/factory/ExoPlayerFactory.kt +32 -0
  20. package/android/src/media3/java/run/granite/video/provider/media3/factory/MediaSourceFactory.kt +61 -0
  21. package/android/src/media3/java/run/granite/video/provider/media3/factory/TrackSelectorFactory.kt +26 -0
  22. package/android/src/media3/java/run/granite/video/provider/media3/factory/VideoSurfaceFactory.kt +62 -0
  23. package/android/src/media3/java/run/granite/video/provider/media3/listener/ExoPlayerEventListener.kt +104 -0
  24. package/android/src/media3/java/run/granite/video/provider/media3/scheduler/ProgressScheduler.kt +56 -0
  25. package/android/src/test/java/run/granite/video/GraniteVideoViewRobolectricTest.kt +598 -0
  26. package/android/src/test/java/run/granite/video/event/VideoEventListenerAdapterTest.kt +319 -0
  27. package/android/src/test/java/run/granite/video/helpers/FakeGraniteVideoProvider.kt +161 -0
  28. package/android/src/test/java/run/granite/video/helpers/TestProgressScheduler.kt +42 -0
  29. package/android/src/test/java/run/granite/video/provider/GraniteVideoRegistryTest.kt +232 -0
  30. package/android/src/test/java/run/granite/video/provider/ProviderContractTest.kt +174 -0
  31. package/android/src/test/java/run/granite/video/provider/media3/listener/ExoPlayerEventListenerTest.kt +243 -0
  32. package/android/src/test/resources/kotest.properties +2 -0
  33. package/dist/module/GraniteVideo.js +458 -0
  34. package/dist/module/GraniteVideo.js.map +1 -0
  35. package/dist/module/GraniteVideoNativeComponent.ts +265 -0
  36. package/dist/module/index.js +7 -0
  37. package/dist/module/index.js.map +1 -0
  38. package/dist/module/package.json +1 -0
  39. package/dist/module/types.js +4 -0
  40. package/dist/module/types.js.map +1 -0
  41. package/dist/typescript/GraniteVideo.d.ts +12 -0
  42. package/dist/typescript/GraniteVideoNativeComponent.d.ts +189 -0
  43. package/dist/typescript/index.d.ts +5 -0
  44. package/dist/typescript/types.d.ts +328 -0
  45. package/ios/GraniteVideoComponentsProvider.h +10 -0
  46. package/ios/GraniteVideoProvider.swift +280 -0
  47. package/ios/GraniteVideoView.h +15 -0
  48. package/ios/GraniteVideoView.mm +661 -0
  49. package/ios/Providers/AVPlayerProvider.swift +541 -0
  50. package/package.json +106 -0
  51. package/src/GraniteVideo.tsx +575 -0
  52. package/src/GraniteVideoNativeComponent.ts +265 -0
  53. package/src/index.ts +8 -0
  54. package/src/types.ts +464 -0
@@ -0,0 +1,328 @@
1
+ import type { StyleProp, ViewStyle, ImageSourcePropType } from 'react-native';
2
+ import type { OnVideoLoadStartEvent, OnVideoLoadEvent, OnVideoProgressEvent, OnVideoSeekEvent, OnVideoBufferEvent, OnVideoBandwidthUpdateEvent, OnVideoPlaybackStateChangedEvent, OnVideoPlaybackRateChangeEvent, OnVideoVolumeChangeEvent, OnVideoAudioFocusChangedEvent, OnVideoPictureInPictureStatusChangedEvent, OnVideoControlsVisibilityChangeEvent, OnVideoExternalPlaybackChangeEvent, OnVideoAspectRatioEvent, OnVideoErrorEvent } from './GraniteVideoNativeComponent';
3
+ export interface VideoSourceHeaders {
4
+ [key: string]: string;
5
+ }
6
+ export interface DrmConfig {
7
+ type: 'widevine' | 'playready' | 'clearkey' | 'fairplay';
8
+ licenseServer?: string;
9
+ headers?: VideoSourceHeaders;
10
+ contentId?: string;
11
+ certificateUrl?: string;
12
+ base64Certificate?: boolean;
13
+ multiDrm?: boolean;
14
+ }
15
+ export interface TextTrack {
16
+ title: string;
17
+ language: string;
18
+ type: 'application/x-subrip' | 'text/vtt' | 'application/ttml+xml';
19
+ uri: string;
20
+ }
21
+ export interface BufferConfig {
22
+ minBufferMs?: number;
23
+ maxBufferMs?: number;
24
+ bufferForPlaybackMs?: number;
25
+ bufferForPlaybackAfterRebufferMs?: number;
26
+ backBufferDurationMs?: number;
27
+ cacheSizeMB?: number;
28
+ live?: {
29
+ maxPlaybackSpeed?: number;
30
+ minPlaybackSpeed?: number;
31
+ maxOffsetMs?: number;
32
+ minOffsetMs?: number;
33
+ targetOffsetMs?: number;
34
+ };
35
+ }
36
+ export interface VideoSource {
37
+ uri?: string;
38
+ type?: string;
39
+ mainVer?: number;
40
+ patchVer?: number;
41
+ headers?: VideoSourceHeaders;
42
+ startPosition?: number;
43
+ cropStart?: number;
44
+ cropEnd?: number;
45
+ drm?: DrmConfig;
46
+ textTracks?: TextTrack[];
47
+ metadata?: VideoMetadata;
48
+ }
49
+ export interface VideoMetadata {
50
+ title?: string;
51
+ subtitle?: string;
52
+ description?: string;
53
+ artist?: string;
54
+ imageUri?: string;
55
+ }
56
+ export type SelectedTrackType = 'system' | 'disabled' | 'title' | 'language' | 'index';
57
+ export type SelectedVideoTrackType = 'auto' | 'disabled' | 'resolution' | 'index';
58
+ export interface SelectedTrack {
59
+ type: SelectedTrackType;
60
+ value?: string | number;
61
+ }
62
+ export interface SelectedVideoTrack {
63
+ type: SelectedVideoTrackType;
64
+ value?: number;
65
+ }
66
+ export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'none';
67
+ export type ViewType = 'surface' | 'texture';
68
+ export interface PosterSource {
69
+ uri?: string;
70
+ }
71
+ export type Poster = ImageSourcePropType | PosterSource;
72
+ export type OnLoadData = OnVideoLoadEvent & {
73
+ naturalSize: OnVideoLoadEvent['naturalSize'] & {
74
+ orientation: 'portrait' | 'landscape';
75
+ };
76
+ audioTracks: AudioTrack[];
77
+ textTracks: TextTrackInfo[];
78
+ videoTracks: VideoTrackInfo[];
79
+ };
80
+ export interface AudioTrack {
81
+ index: number;
82
+ title?: string;
83
+ language?: string;
84
+ bitrate?: number;
85
+ type?: string;
86
+ selected?: boolean;
87
+ }
88
+ export interface TextTrackInfo {
89
+ index: number;
90
+ title?: string;
91
+ language?: string;
92
+ type?: string;
93
+ selected?: boolean;
94
+ }
95
+ export interface VideoTrackInfo {
96
+ index: number;
97
+ trackId?: string;
98
+ codecs?: string;
99
+ width?: number;
100
+ height?: number;
101
+ bitrate?: number;
102
+ selected?: boolean;
103
+ }
104
+ export type OnSeekData = OnVideoSeekEvent & {
105
+ target?: number;
106
+ };
107
+ export interface OnTimedMetadataData {
108
+ metadata: Array<{
109
+ value: string;
110
+ identifier: string;
111
+ }>;
112
+ }
113
+ export interface OnAudioTracksData {
114
+ audioTracks: AudioTrack[];
115
+ }
116
+ export interface OnTextTracksData {
117
+ textTracks: TextTrackInfo[];
118
+ }
119
+ export interface OnTextTrackDataChangedData {
120
+ subtitleTracks: string;
121
+ }
122
+ export interface OnVideoTracksData {
123
+ videoTracks: VideoTrackInfo[];
124
+ }
125
+ export interface OnReceiveAdEventData {
126
+ event: string;
127
+ data?: {
128
+ [key: string]: string | number | boolean;
129
+ };
130
+ }
131
+ export interface OnTransferEndData {
132
+ uri: string;
133
+ bytesTransferred: number;
134
+ }
135
+ export interface VideoRef {
136
+ seek: (time: number, tolerance?: number) => void;
137
+ pause: () => void;
138
+ resume: () => void;
139
+ setVolume: (volume: number) => void;
140
+ setFullScreen: (fullscreen: boolean) => void;
141
+ presentFullscreenPlayer: () => void;
142
+ dismissFullscreenPlayer: () => void;
143
+ enterPictureInPicture: () => void;
144
+ exitPictureInPicture: () => void;
145
+ setSource: (source: VideoSource) => void;
146
+ getCurrentPosition: () => Promise<number>;
147
+ save: (options?: {
148
+ type?: string;
149
+ }) => Promise<{
150
+ uri: string;
151
+ }>;
152
+ restoreUserInterfaceForPictureInPictureStopCompleted: (restored: boolean) => void;
153
+ }
154
+ export interface VideoProps {
155
+ testID?: string;
156
+ style?: StyleProp<ViewStyle>;
157
+ progressUpdateInterval?: number;
158
+ source: VideoSource | number;
159
+ /**
160
+ * **NOTE**
161
+ *
162
+ * Value: string with a URL for the poster is deprecated, use poster as an object instead.
163
+ */
164
+ poster?: Poster | string;
165
+ posterResizeMode?: ResizeMode;
166
+ paused?: boolean;
167
+ muted?: boolean;
168
+ volume?: number;
169
+ rate?: number;
170
+ repeat?: boolean;
171
+ playInBackground?: boolean;
172
+ playWhenInactive?: boolean;
173
+ automaticallyWaitsToMinimizeStalling?: boolean;
174
+ shutterColor?: string;
175
+ resizeMode?: ResizeMode;
176
+ viewType?: ViewType;
177
+ useTextureView?: boolean;
178
+ useSecureView?: boolean;
179
+ bufferConfig?: BufferConfig;
180
+ minLoadRetryCount?: number;
181
+ maxBitRate?: number;
182
+ preferredForwardBufferDuration?: number;
183
+ selectedAudioTrack?: SelectedTrack;
184
+ selectedTextTrack?: SelectedTrack;
185
+ selectedVideoTrack?: SelectedVideoTrack;
186
+ textTracks?: TextTrack[];
187
+ drm?: DrmConfig;
188
+ localSourceEncryptionKeyScheme?: string;
189
+ adTagUrl?: string;
190
+ adLanguage?: string;
191
+ controls?: boolean;
192
+ showNotificationControls?: boolean;
193
+ disableFocus?: boolean;
194
+ disableDisconnectError?: boolean;
195
+ focusable?: boolean;
196
+ hideShutterView?: boolean;
197
+ preventsDisplaySleepDuringVideoPlayback?: boolean;
198
+ fullscreen?: boolean;
199
+ fullscreenAutorotate?: boolean;
200
+ fullscreenOrientation?: 'all' | 'landscape' | 'portrait';
201
+ pictureInPicture?: boolean;
202
+ contentStartTime?: number;
203
+ allowsExternalPlayback?: boolean;
204
+ audioOutput?: 'speaker' | 'earpiece';
205
+ ignoreSilentSwitch?: 'inherit' | 'ignore' | 'obey';
206
+ mixWithOthers?: 'inherit' | 'mix' | 'duck';
207
+ debug?: {
208
+ enable?: boolean;
209
+ thread?: boolean;
210
+ };
211
+ /**
212
+ * Callback when video starts loading
213
+ */
214
+ onLoadStart?: (data: OnVideoLoadStartEvent) => void;
215
+ /**
216
+ * Callback when video is loaded
217
+ */
218
+ onLoad?: (data: OnLoadData) => void;
219
+ /**
220
+ * Callback when video fails to load
221
+ */
222
+ onError?: (data: OnVideoErrorEvent) => void;
223
+ /**
224
+ * Callback during video playback progress
225
+ */
226
+ onProgress?: (data: OnVideoProgressEvent) => void;
227
+ /**
228
+ * Callback when video seek completes
229
+ */
230
+ onSeek?: (data: OnSeekData) => void;
231
+ /**
232
+ * Callback when video ends
233
+ */
234
+ onEnd?: () => void;
235
+ /**
236
+ * Callback when video buffering state changes
237
+ */
238
+ onBuffer?: (data: OnVideoBufferEvent) => void;
239
+ /**
240
+ * Callback when video bandwidth updates
241
+ */
242
+ onBandwidthUpdate?: (data: OnVideoBandwidthUpdateEvent) => void;
243
+ /**
244
+ * Callback when playback state changes
245
+ */
246
+ onPlaybackStateChanged?: (data: OnVideoPlaybackStateChangedEvent) => void;
247
+ /**
248
+ * Callback when playback rate changes
249
+ */
250
+ onPlaybackRateChange?: (data: OnVideoPlaybackRateChangeEvent) => void;
251
+ /**
252
+ * Callback when volume changes
253
+ */
254
+ onVolumeChange?: (data: OnVideoVolumeChangeEvent) => void;
255
+ /**
256
+ * Callback when video becomes idle
257
+ */
258
+ onIdle?: () => void;
259
+ /**
260
+ * Callback when video is ready for display
261
+ */
262
+ onReadyForDisplay?: () => void;
263
+ onPlaybackResume?: () => void;
264
+ onPlaybackStalled?: () => void;
265
+ onAudioTracks?: (data: OnAudioTracksData) => void;
266
+ onTextTracks?: (data: OnTextTracksData) => void;
267
+ onTextTrackDataChanged?: (data: OnTextTrackDataChangedData) => void;
268
+ onVideoTracks?: (data: OnVideoTracksData) => void;
269
+ onTimedMetadata?: (data: OnTimedMetadataData) => void;
270
+ /**
271
+ * Callback when video aspect ratio changes
272
+ */
273
+ onAspectRatio?: (data: OnVideoAspectRatioEvent) => void;
274
+ /**
275
+ * Callback when audio focus changes
276
+ */
277
+ onAudioFocusChanged?: (data: OnVideoAudioFocusChangedEvent) => void;
278
+ /**
279
+ * Callback when audio becomes noisy
280
+ */
281
+ onAudioBecomingNoisy?: () => void;
282
+ /**
283
+ * Callback before fullscreen player presents
284
+ */
285
+ onFullscreenPlayerWillPresent?: () => void;
286
+ /**
287
+ * Callback after fullscreen player presents
288
+ */
289
+ onFullscreenPlayerDidPresent?: () => void;
290
+ /**
291
+ * Callback before fullscreen player dismisses
292
+ */
293
+ onFullscreenPlayerWillDismiss?: () => void;
294
+ /**
295
+ * Callback after fullscreen player dismisses
296
+ */
297
+ onFullscreenPlayerDidDismiss?: () => void;
298
+ /**
299
+ * Callback when picture-in-picture status changes
300
+ */
301
+ onPictureInPictureStatusChanged?: (data: OnVideoPictureInPictureStatusChangedEvent) => void;
302
+ /**
303
+ * Callback to restore user interface for picture-in-picture stop
304
+ */
305
+ onRestoreUserInterfaceForPictureInPictureStop?: () => void;
306
+ /**
307
+ * Callback when controls visibility changes
308
+ */
309
+ onControlsVisibilityChange?: (data: OnVideoControlsVisibilityChangeEvent) => void;
310
+ /**
311
+ * Callback when external playback changes
312
+ */
313
+ onExternalPlaybackChange?: (data: OnVideoExternalPlaybackChangeEvent) => void;
314
+ /**
315
+ * Callback when receive ad event occurs
316
+ */
317
+ onReceiveAdEvent?: (data: OnReceiveAdEventData) => void;
318
+ /**
319
+ * Callback when transfer end event occurs
320
+ */
321
+ onTransferEnd?: (data: OnTransferEndData) => void;
322
+ }
323
+ export interface VideoStatic {
324
+ clearCache: () => Promise<void>;
325
+ getWidevineLevel: () => Promise<number>;
326
+ isCodecSupported: (mimeType: string, width: number, height: number) => Promise<boolean>;
327
+ isHEVCSupported: () => Promise<boolean>;
328
+ }
@@ -0,0 +1,10 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ // Forward declaration to avoid pulling in React C++ headers
6
+ // The actual implementation is in the .mm file
7
+ @protocol RCTComponentViewProtocol;
8
+ Class<RCTComponentViewProtocol> GraniteVideoViewCls(void) __attribute__((used));
9
+
10
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,280 @@
1
+ import UIKit
2
+ import AVFoundation
3
+
4
+ // ============================================================
5
+ // MARK: - Enums
6
+ // ============================================================
7
+
8
+ @objc public enum GraniteVideoResizeMode: Int {
9
+ case contain = 0
10
+ case cover = 1
11
+ case stretch = 2
12
+ case none = 3
13
+ }
14
+
15
+ @objc public enum GraniteVideoDrmType: Int {
16
+ case none = 0
17
+ case fairplay = 1
18
+ case widevine = 2
19
+ case playready = 3
20
+ case clearkey = 4
21
+ }
22
+
23
+ @objc public enum GraniteVideoAudioOutput: Int {
24
+ case speaker = 0
25
+ case earpiece = 1
26
+ }
27
+
28
+ @objc public enum GraniteVideoIgnoreSilentSwitch: Int {
29
+ case inherit = 0
30
+ case ignore = 1
31
+ case obey = 2
32
+ }
33
+
34
+ @objc public enum GraniteVideoMixWithOthers: Int {
35
+ case inherit = 0
36
+ case mix = 1
37
+ case duck = 2
38
+ }
39
+
40
+ // ============================================================
41
+ // MARK: - Data Types
42
+ // ============================================================
43
+
44
+ @objc public class GraniteVideoDrmConfig: NSObject {
45
+ @objc public var type: GraniteVideoDrmType = .none
46
+ @objc public var licenseServer: String?
47
+ @objc public var headers: [String: String]?
48
+ @objc public var contentID: String?
49
+ @objc public var certificateURL: String?
50
+ @objc public var base64Certificate: Bool = false
51
+ }
52
+
53
+ @objc public class GraniteVideoSource: NSObject {
54
+ @objc public var uri: String?
55
+ @objc public var type: String?
56
+ @objc public var startPosition: Double = 0
57
+ @objc public var cropStart: Double = 0
58
+ @objc public var cropEnd: Double = 0
59
+ @objc public var headers: [String: String]?
60
+ @objc public var drm: GraniteVideoDrmConfig?
61
+
62
+ @objc public override init() {
63
+ super.init()
64
+ }
65
+
66
+ @objc public convenience init(uri: String) {
67
+ self.init()
68
+ self.uri = uri
69
+ }
70
+ }
71
+
72
+ @objc public class GraniteVideoBufferConfig: NSObject {
73
+ @objc public var minBufferMs: Int = 15000
74
+ @objc public var maxBufferMs: Int = 50000
75
+ @objc public var bufferForPlaybackMs: Int = 2500
76
+ @objc public var bufferForPlaybackAfterRebufferMs: Int = 5000
77
+ @objc public var cacheSizeMB: Int = 0
78
+ }
79
+
80
+ @objc public class GraniteVideoSelectedTrack: NSObject {
81
+ @objc public var type: String = "system"
82
+ @objc public var value: String?
83
+ }
84
+
85
+ // ============================================================
86
+ // MARK: - Event Data Types
87
+ // ============================================================
88
+
89
+ @objc public class GraniteVideoLoadData: NSObject {
90
+ @objc public var currentTime: Double = 0
91
+ @objc public var duration: Double = 0
92
+ @objc public var naturalWidth: Double = 0
93
+ @objc public var naturalHeight: Double = 0
94
+ @objc public var orientation: String = "landscape"
95
+ }
96
+
97
+ @objc public class GraniteVideoProgressData: NSObject {
98
+ @objc public var currentTime: Double = 0
99
+ @objc public var playableDuration: Double = 0
100
+ @objc public var seekableDuration: Double = 0
101
+ }
102
+
103
+ @objc public class GraniteVideoErrorData: NSObject {
104
+ @objc public var code: Int = 0
105
+ @objc public var domain: String = ""
106
+ @objc public var localizedDescription_: String = ""
107
+ @objc public var localizedFailureReason_: String = ""
108
+ @objc public var localizedRecoverySuggestion_: String = ""
109
+
110
+ @objc public override init() {
111
+ super.init()
112
+ }
113
+
114
+ @objc public convenience init(error: Error) {
115
+ self.init()
116
+ let nsError = error as NSError
117
+ self.code = nsError.code
118
+ self.domain = nsError.domain
119
+ self.localizedDescription_ = nsError.localizedDescription
120
+ self.localizedFailureReason_ = nsError.localizedFailureReason ?? ""
121
+ self.localizedRecoverySuggestion_ = nsError.localizedRecoverySuggestion ?? ""
122
+ }
123
+ }
124
+
125
+ // ============================================================
126
+ // MARK: - Delegate Protocol
127
+ // ============================================================
128
+
129
+ @objc public protocol GraniteVideoDelegate: AnyObject {
130
+ @objc optional func videoDidLoadStart(isNetwork: Bool, type: String, uri: String)
131
+ @objc optional func videoDidLoad(data: GraniteVideoLoadData)
132
+ @objc optional func videoDidFail(error: GraniteVideoErrorData)
133
+ @objc optional func videoDidUpdateProgress(data: GraniteVideoProgressData)
134
+ @objc optional func videoDidSeek(currentTime: Double, seekTime: Double)
135
+ @objc optional func videoDidEnd()
136
+ @objc optional func videoBufferingStateChanged(isBuffering: Bool)
137
+ @objc optional func videoBandwidthDidUpdate(bitrate: Double, width: Int, height: Int)
138
+ @objc optional func videoPlaybackStateChanged(isPlaying: Bool, isSeeking: Bool, isLooping: Bool)
139
+ @objc optional func videoPlaybackRateChanged(rate: Float)
140
+ @objc optional func videoVolumeChanged(volume: Float)
141
+ @objc optional func videoDidBecomeIdle()
142
+ @objc optional func videoReadyForDisplay()
143
+ @objc optional func videoAudioBecomingNoisy()
144
+ @objc optional func videoFullscreenPlayerWillPresent()
145
+ @objc optional func videoFullscreenPlayerDidPresent()
146
+ @objc optional func videoFullscreenPlayerWillDismiss()
147
+ @objc optional func videoFullscreenPlayerDidDismiss()
148
+ @objc optional func videoPictureInPictureStatusChanged(isActive: Bool)
149
+ @objc optional func videoRestoreUserInterfaceForPictureInPictureStop()
150
+ @objc optional func videoControlsVisibilityChanged(isVisible: Bool)
151
+ @objc optional func videoExternalPlaybackChanged(isActive: Bool)
152
+ @objc optional func videoAspectRatioChanged(width: Double, height: Double)
153
+ @objc optional func videoTransferEnd(uri: String, bytesTransferred: Double)
154
+ }
155
+
156
+ // ============================================================
157
+ // MARK: - Provider Protocol
158
+ // ============================================================
159
+
160
+ @objc public protocol GraniteVideoProvidable: NSObjectProtocol {
161
+ // Required - View Creation
162
+ @objc func createPlayerView() -> UIView
163
+
164
+ // Required - Source Loading
165
+ @objc func loadSource(_ source: GraniteVideoSource)
166
+ @objc func unload()
167
+
168
+ // Required - Playback Control
169
+ @objc func play()
170
+ @objc func pause()
171
+ @objc func seek(to time: Double, toleranceBefore: Double, toleranceAfter: Double)
172
+
173
+ // Required - Properties
174
+ @objc weak var delegate: GraniteVideoDelegate? { get set }
175
+ @objc var currentTime: Double { get }
176
+ @objc var duration: Double { get }
177
+ @objc var isPlaying: Bool { get }
178
+
179
+ // Optional - Volume
180
+ @objc optional func setVolume(_ volume: Float)
181
+ @objc optional func setMuted(_ muted: Bool)
182
+
183
+ // Optional - Rate
184
+ @objc optional func setRate(_ rate: Float)
185
+
186
+ // Optional - Repeat
187
+ @objc optional func setRepeat(_ shouldRepeat: Bool)
188
+
189
+ // Optional - Resize Mode
190
+ @objc optional func setResizeMode(_ mode: GraniteVideoResizeMode)
191
+
192
+ // Optional - Background Playback
193
+ @objc optional func setPlayInBackground(_ enabled: Bool)
194
+ @objc optional func setPlayWhenInactive(_ enabled: Bool)
195
+
196
+ // Optional - Audio Session
197
+ @objc optional func setAudioOutput(_ output: GraniteVideoAudioOutput)
198
+ @objc optional func setIgnoreSilentSwitch(_ mode: GraniteVideoIgnoreSilentSwitch)
199
+ @objc optional func setMixWithOthers(_ mode: GraniteVideoMixWithOthers)
200
+
201
+ // Optional - Fullscreen
202
+ @objc optional func setFullscreen(_ fullscreen: Bool, animated: Bool)
203
+ @objc optional func setFullscreenAutorotate(_ autorotate: Bool)
204
+ @objc optional func setFullscreenOrientation(_ orientation: String)
205
+
206
+ // Optional - Picture in Picture
207
+ @objc optional func setPictureInPictureEnabled(_ enabled: Bool)
208
+ @objc optional func enterPictureInPicture()
209
+ @objc optional func exitPictureInPicture()
210
+
211
+ // Optional - Controls
212
+ @objc optional func setControlsEnabled(_ enabled: Bool)
213
+ @objc optional func setPreventsDisplaySleepDuringVideoPlayback(_ prevents: Bool)
214
+
215
+ // Optional - Buffer Config
216
+ @objc optional func setBufferConfig(_ config: GraniteVideoBufferConfig)
217
+ @objc optional func setMaxBitRate(_ bitRate: Int)
218
+ @objc optional func setPreferredForwardBufferDuration(_ duration: Double)
219
+ @objc optional func setAutomaticallyWaitsToMinimizeStalling(_ waits: Bool)
220
+
221
+ // Optional - Track Selection
222
+ @objc optional func setSelectedAudioTrack(_ track: GraniteVideoSelectedTrack)
223
+ @objc optional func setSelectedTextTrack(_ track: GraniteVideoSelectedTrack)
224
+ @objc optional func setSelectedVideoTrack(type: String, value: Int)
225
+
226
+ // Optional - External Playback
227
+ @objc optional func setAllowsExternalPlayback(_ allows: Bool)
228
+
229
+ // Optional - DRM
230
+ @objc optional func setDrmConfig(_ config: GraniteVideoDrmConfig)
231
+
232
+ // Optional - Poster
233
+ @objc optional func setPoster(_ url: String, resizeMode: GraniteVideoResizeMode)
234
+
235
+ // Optional - Save
236
+ @objc optional func save(type: String, completion: @escaping (String?, Error?) -> Void)
237
+
238
+ // Optional - Cache Management
239
+ @objc optional func clearCache()
240
+ }
241
+
242
+ // ============================================================
243
+ // MARK: - Registry
244
+ // ============================================================
245
+
246
+ public typealias GraniteVideoProviderFactory = () -> GraniteVideoProvidable
247
+
248
+ @objc public class GraniteVideoRegistry: NSObject {
249
+ @objc public static let shared = GraniteVideoRegistry()
250
+
251
+ private var internalProvider: GraniteVideoProvidable?
252
+ private var providerFactory: GraniteVideoProviderFactory?
253
+
254
+ private override init() {
255
+ super.init()
256
+ }
257
+
258
+ @objc public var provider: GraniteVideoProvidable? {
259
+ return internalProvider
260
+ }
261
+
262
+ @objc public func register(provider: GraniteVideoProvidable) {
263
+ internalProvider = provider
264
+ }
265
+
266
+ @objc public func register(factory: @escaping GraniteVideoProviderFactory) {
267
+ providerFactory = factory
268
+ }
269
+
270
+ @objc public func createProvider() -> GraniteVideoProvidable? {
271
+ if let factory = providerFactory {
272
+ return factory()
273
+ }
274
+ return internalProvider
275
+ }
276
+
277
+ @objc public func hasProvider() -> Bool {
278
+ return internalProvider != nil || providerFactory != nil
279
+ }
280
+ }
@@ -0,0 +1,15 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ #ifndef GraniteVideoViewNativeComponent_h
4
+ #define GraniteVideoViewNativeComponent_h
5
+
6
+ NS_ASSUME_NONNULL_BEGIN
7
+
8
+ // Forward declare to avoid pulling in C++ headers
9
+ // RCTViewComponentView import is in the .mm file
10
+ @interface GraniteVideoView : UIView
11
+ @end
12
+
13
+ NS_ASSUME_NONNULL_END
14
+
15
+ #endif /* GraniteVideoViewNativeComponent_h */