@stepincto/expo-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.
- package/README.md +45 -0
- package/android/build.gradle +32 -0
- package/android/src/main/AndroidManifest.xml +20 -0
- package/android/src/main/java/expo/modules/video/AudioFocusManager.kt +241 -0
- package/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt +145 -0
- package/android/src/main/java/expo/modules/video/IntervalUpdateClock.kt +54 -0
- package/android/src/main/java/expo/modules/video/MediaMetadataRetriever.kt +89 -0
- package/android/src/main/java/expo/modules/video/PictureInPictureHelperFragment.kt +26 -0
- package/android/src/main/java/expo/modules/video/PlayerViewExtension.kt +36 -0
- package/android/src/main/java/expo/modules/video/VideoCache.kt +104 -0
- package/android/src/main/java/expo/modules/video/VideoExceptions.kt +34 -0
- package/android/src/main/java/expo/modules/video/VideoManager.kt +133 -0
- package/android/src/main/java/expo/modules/video/VideoModule.kt +414 -0
- package/android/src/main/java/expo/modules/video/VideoThumbnail.kt +20 -0
- package/android/src/main/java/expo/modules/video/VideoView.kt +367 -0
- package/android/src/main/java/expo/modules/video/delegates/IgnoreSameSet.kt +24 -0
- package/android/src/main/java/expo/modules/video/drawing/OutlineProvider.kt +217 -0
- package/android/src/main/java/expo/modules/video/enums/AudioMixingMode.kt +20 -0
- package/android/src/main/java/expo/modules/video/enums/ContentFit.kt +19 -0
- package/android/src/main/java/expo/modules/video/enums/ContentType.kt +22 -0
- package/android/src/main/java/expo/modules/video/enums/DRMType.kt +26 -0
- package/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt +10 -0
- package/android/src/main/java/expo/modules/video/playbackService/ExpoVideoPlaybackService.kt +184 -0
- package/android/src/main/java/expo/modules/video/playbackService/PlaybackServiceConnection.kt +39 -0
- package/android/src/main/java/expo/modules/video/playbackService/VideoMediaSessionCallback.kt +47 -0
- package/android/src/main/java/expo/modules/video/player/FirstFrameEventGenerator.kt +93 -0
- package/android/src/main/java/expo/modules/video/player/PlayerEvent.kt +164 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayer.kt +460 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerAudioTracks.kt +125 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerListener.kt +32 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerLoadControl.kt +525 -0
- package/android/src/main/java/expo/modules/video/player/VideoPlayerSubtitles.kt +125 -0
- package/android/src/main/java/expo/modules/video/records/BufferOptions.kt +15 -0
- package/android/src/main/java/expo/modules/video/records/DRMOptions.kt +25 -0
- package/android/src/main/java/expo/modules/video/records/PlaybackError.kt +19 -0
- package/android/src/main/java/expo/modules/video/records/Tracks.kt +81 -0
- package/android/src/main/java/expo/modules/video/records/VideoEventPayloads.kt +79 -0
- package/android/src/main/java/expo/modules/video/records/VideoMetadata.kt +12 -0
- package/android/src/main/java/expo/modules/video/records/VideoSize.kt +14 -0
- package/android/src/main/java/expo/modules/video/records/VideoSource.kt +104 -0
- package/android/src/main/java/expo/modules/video/records/VideoThumbnailOptions.kt +24 -0
- package/android/src/main/java/expo/modules/video/utils/DataSourceUtils.kt +75 -0
- package/android/src/main/java/expo/modules/video/utils/EventDispatcherUtils.kt +43 -0
- package/android/src/main/java/expo/modules/video/utils/MutableWeakReference.kt +15 -0
- package/android/src/main/java/expo/modules/video/utils/PictureInPictureUtils.kt +96 -0
- package/android/src/main/java/expo/modules/video/utils/YogaUtils.kt +20 -0
- package/android/src/main/res/drawable/seek_backwards_10s.xml +25 -0
- package/android/src/main/res/drawable/seek_backwards_15s.xml +25 -0
- package/android/src/main/res/drawable/seek_backwards_5s.xml +25 -0
- package/android/src/main/res/drawable/seek_forwards_10s.xml +30 -0
- package/android/src/main/res/drawable/seek_forwards_15s.xml +31 -0
- package/android/src/main/res/drawable/seek_forwards_5s.xml +30 -0
- package/android/src/main/res/layout/fullscreen_player_activity.xml +16 -0
- package/android/src/main/res/layout/surface_player_view.xml +7 -0
- package/android/src/main/res/layout/texture_player_view.xml +7 -0
- package/android/src/main/res/values/styles.xml +9 -0
- package/app.plugin.js +1 -0
- package/build/NativeVideoModule.d.ts +16 -0
- package/build/NativeVideoModule.d.ts.map +1 -0
- package/build/NativeVideoModule.js +3 -0
- package/build/NativeVideoModule.js.map +1 -0
- package/build/NativeVideoModule.web.d.ts +3 -0
- package/build/NativeVideoModule.web.d.ts.map +1 -0
- package/build/NativeVideoModule.web.js +2 -0
- package/build/NativeVideoModule.web.js.map +1 -0
- package/build/NativeVideoView.d.ts +4 -0
- package/build/NativeVideoView.d.ts.map +1 -0
- package/build/NativeVideoView.js +6 -0
- package/build/NativeVideoView.js.map +1 -0
- package/build/VideoModule.d.ts +38 -0
- package/build/VideoModule.d.ts.map +1 -0
- package/build/VideoModule.js +53 -0
- package/build/VideoModule.js.map +1 -0
- package/build/VideoPlayer.d.ts +15 -0
- package/build/VideoPlayer.d.ts.map +1 -0
- package/build/VideoPlayer.js +52 -0
- package/build/VideoPlayer.js.map +1 -0
- package/build/VideoPlayer.types.d.ts +532 -0
- package/build/VideoPlayer.types.d.ts.map +1 -0
- package/build/VideoPlayer.types.js +2 -0
- package/build/VideoPlayer.types.js.map +1 -0
- package/build/VideoPlayer.web.d.ts +75 -0
- package/build/VideoPlayer.web.d.ts.map +1 -0
- package/build/VideoPlayer.web.js +376 -0
- package/build/VideoPlayer.web.js.map +1 -0
- package/build/VideoPlayerEvents.types.d.ts +262 -0
- package/build/VideoPlayerEvents.types.d.ts.map +1 -0
- package/build/VideoPlayerEvents.types.js +2 -0
- package/build/VideoPlayerEvents.types.js.map +1 -0
- package/build/VideoThumbnail.d.ts +29 -0
- package/build/VideoThumbnail.d.ts.map +1 -0
- package/build/VideoThumbnail.js +3 -0
- package/build/VideoThumbnail.js.map +1 -0
- package/build/VideoView.d.ts +44 -0
- package/build/VideoView.d.ts.map +1 -0
- package/build/VideoView.js +76 -0
- package/build/VideoView.js.map +1 -0
- package/build/VideoView.types.d.ts +147 -0
- package/build/VideoView.types.d.ts.map +1 -0
- package/build/VideoView.types.js +2 -0
- package/build/VideoView.types.js.map +1 -0
- package/build/VideoView.web.d.ts +9 -0
- package/build/VideoView.web.d.ts.map +1 -0
- package/build/VideoView.web.js +180 -0
- package/build/VideoView.web.js.map +1 -0
- package/build/index.d.ts +9 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/build/resolveAssetSource.d.ts +3 -0
- package/build/resolveAssetSource.d.ts.map +1 -0
- package/build/resolveAssetSource.js +3 -0
- package/build/resolveAssetSource.js.map +1 -0
- package/build/resolveAssetSource.web.d.ts +4 -0
- package/build/resolveAssetSource.web.d.ts.map +1 -0
- package/build/resolveAssetSource.web.js +16 -0
- package/build/resolveAssetSource.web.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/Cache/CachableRequest.swift +44 -0
- package/ios/Cache/CachedResource.swift +97 -0
- package/ios/Cache/CachingHelpers.swift +92 -0
- package/ios/Cache/MediaFileHandle.swift +94 -0
- package/ios/Cache/MediaInfo.swift +147 -0
- package/ios/Cache/ResourceLoaderDelegate.swift +274 -0
- package/ios/Cache/SynchronizedHashTable.swift +23 -0
- package/ios/Cache/VideoCacheManager.swift +338 -0
- package/ios/ContentKeyDelegate.swift +214 -0
- package/ios/ContentKeyManager.swift +21 -0
- package/ios/Enums/AudioMixingMode.swift +37 -0
- package/ios/Enums/ContentType.swift +12 -0
- package/ios/Enums/DRMType.swift +20 -0
- package/ios/Enums/PlayerStatus.swift +10 -0
- package/ios/Enums/VideoContentFit.swift +39 -0
- package/ios/ExpoVideo.podspec +29 -0
- package/ios/NowPlayingManager.swift +296 -0
- package/ios/Records/BufferOptions.swift +12 -0
- package/ios/Records/DRMOptions.swift +24 -0
- package/ios/Records/PlaybackError.swift +10 -0
- package/ios/Records/Tracks.swift +176 -0
- package/ios/Records/VideoEventPayloads.swift +76 -0
- package/ios/Records/VideoMetadata.swift +16 -0
- package/ios/Records/VideoSize.swift +15 -0
- package/ios/Records/VideoSource.swift +25 -0
- package/ios/Thumbnails/VideoThumbnail.swift +27 -0
- package/ios/Thumbnails/VideoThumbnailGenerator.swift +68 -0
- package/ios/Thumbnails/VideoThumbnailOptions.swift +15 -0
- package/ios/VideoAsset.swift +123 -0
- package/ios/VideoExceptions.swift +53 -0
- package/ios/VideoItem.swift +11 -0
- package/ios/VideoManager.swift +140 -0
- package/ios/VideoModule.swift +383 -0
- package/ios/VideoPlayer/DangerousPropertiesStore.swift +19 -0
- package/ios/VideoPlayer.swift +435 -0
- package/ios/VideoPlayerAudioTracks.swift +72 -0
- package/ios/VideoPlayerItem.swift +97 -0
- package/ios/VideoPlayerObserver.swift +523 -0
- package/ios/VideoPlayerSubtitles.swift +71 -0
- package/ios/VideoSourceLoader.swift +89 -0
- package/ios/VideoSourceLoaderListener.swift +34 -0
- package/ios/VideoView.swift +224 -0
- package/package.json +59 -0
- package/plugin/build/tsconfig.tsbuildinfo +1 -0
- package/plugin/build/withExpoVideo.d.ts +7 -0
- package/plugin/build/withExpoVideo.js +38 -0
- package/src/NativeVideoModule.ts +20 -0
- package/src/NativeVideoModule.web.ts +1 -0
- package/src/NativeVideoView.ts +8 -0
- package/src/VideoModule.ts +59 -0
- package/src/VideoPlayer.tsx +67 -0
- package/src/VideoPlayer.types.ts +613 -0
- package/src/VideoPlayer.web.tsx +451 -0
- package/src/VideoPlayerEvents.types.ts +313 -0
- package/src/VideoThumbnail.ts +31 -0
- package/src/VideoView.tsx +86 -0
- package/src/VideoView.types.ts +165 -0
- package/src/VideoView.web.tsx +214 -0
- package/src/index.ts +46 -0
- package/src/resolveAssetSource.ts +2 -0
- package/src/resolveAssetSource.web.ts +17 -0
- package/src/ts-declarations/react-native-assets.d.ts +1 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
// Copyright 2023-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoModulesCore
|
|
4
|
+
import AVKit
|
|
5
|
+
|
|
6
|
+
public final class VideoModule: Module {
|
|
7
|
+
public func definition() -> ModuleDefinition {
|
|
8
|
+
Name("ExpoVideo")
|
|
9
|
+
|
|
10
|
+
Function("isPictureInPictureSupported") { () -> Bool in
|
|
11
|
+
return AVPictureInPictureController.isPictureInPictureSupported()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
Function("getCurrentVideoCacheSize") {
|
|
15
|
+
VideoCacheManager.shared.getCacheDirectorySize()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
AsyncFunction("setVideoCacheSizeAsync") { size in
|
|
19
|
+
try VideoCacheManager.shared.setMaxCacheSize(newSize: size)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
AsyncFunction("clearVideoCacheAsync") {
|
|
23
|
+
return try await VideoCacheManager.shared.clearAllCache()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
AsyncFunction("preCacheVideoAsync") { (urlString: String) -> Bool in
|
|
27
|
+
return try await VideoCacheManager.preCacheVideoAsync(from: urlString)
|
|
28
|
+
}
|
|
29
|
+
AsyncFunction("preCacheVideoPartialAsync") { (urlString: String, chunkSize: Int?) -> Bool in
|
|
30
|
+
return try await VideoCacheManager.preCacheVideoPartialAsync(from: urlString, chunkSize: chunkSize ?? 1_048_576)
|
|
31
|
+
}
|
|
32
|
+
AsyncFunction("isVideoCachedAsync") { (urlString: String) -> Bool in
|
|
33
|
+
guard let url = URL(string: urlString) else {
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
let fileExtension = url.pathExtension
|
|
37
|
+
guard let saveFilePath = VideoAsset.pathForUrl(url: url, fileExtension: fileExtension) else {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
let mediaInfoPath = saveFilePath + VideoCacheManager.mediaInfoSuffix
|
|
41
|
+
guard let mediaInfo = MediaInfo(at: mediaInfoPath) else {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
let expectedLength = Int(mediaInfo.expectedContentLength)
|
|
45
|
+
let ranges = mediaInfo.loadedDataRanges
|
|
46
|
+
if ranges.count == 1, ranges[0].0 == 0, ranges[0].1 == expectedLength - 1 {
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
View(VideoView.self) {
|
|
53
|
+
Events(
|
|
54
|
+
"onPictureInPictureStart",
|
|
55
|
+
"onPictureInPictureStop",
|
|
56
|
+
"onFullscreenEnter",
|
|
57
|
+
"onFullscreenExit",
|
|
58
|
+
"onFirstFrameRender"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
Prop("player") { (view, player: VideoPlayer?) in
|
|
62
|
+
view.player = player
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Prop("nativeControls") { (view, nativeControls: Bool?) in
|
|
66
|
+
view.playerViewController.showsPlaybackControls = nativeControls ?? true
|
|
67
|
+
#if os(tvOS)
|
|
68
|
+
view.playerViewController.isSkipForwardEnabled = nativeControls ?? true
|
|
69
|
+
view.playerViewController.isSkipBackwardEnabled = nativeControls ?? true
|
|
70
|
+
#endif
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
Prop("contentFit") { (view, contentFit: VideoContentFit?) in
|
|
74
|
+
view.playerViewController.videoGravity = contentFit?.toVideoGravity() ?? .resizeAspect
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Prop("contentPosition") { (view, contentPosition: CGVector?) in
|
|
78
|
+
let layer = view.playerViewController.view.layer
|
|
79
|
+
|
|
80
|
+
layer.frame = CGRect(
|
|
81
|
+
x: contentPosition?.dx ?? 0,
|
|
82
|
+
y: contentPosition?.dy ?? 0,
|
|
83
|
+
width: layer.frame.width,
|
|
84
|
+
height: layer.frame.height
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Prop("allowsFullscreen") { (view, allowsFullscreen: Bool?) in
|
|
89
|
+
#if !os(tvOS)
|
|
90
|
+
view.playerViewController.setValue(allowsFullscreen ?? true, forKey: "allowsEnteringFullScreen")
|
|
91
|
+
#endif
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Prop("showsTimecodes") { (view, showsTimecodes: Bool?) in
|
|
95
|
+
#if !os(tvOS)
|
|
96
|
+
view.playerViewController.showsTimecodes = showsTimecodes ?? true
|
|
97
|
+
#endif
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
Prop("requiresLinearPlayback") { (view, requiresLinearPlayback: Bool?) in
|
|
101
|
+
view.playerViewController.requiresLinearPlayback = requiresLinearPlayback ?? false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Prop("allowsPictureInPicture") { (view, allowsPictureInPicture: Bool?) in
|
|
105
|
+
view.allowPictureInPicture = allowsPictureInPicture ?? false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Prop("startsPictureInPictureAutomatically") { (view, startsPictureInPictureAutomatically: Bool?) in
|
|
109
|
+
#if !os(tvOS)
|
|
110
|
+
view.startPictureInPictureAutomatically = startsPictureInPictureAutomatically ?? false
|
|
111
|
+
#endif
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Prop("allowsVideoFrameAnalysis") { (view, allowsVideoFrameAnalysis: Bool?) in
|
|
115
|
+
#if !os(tvOS)
|
|
116
|
+
if #available(iOS 16.0, macCatalyst 18.0, *) {
|
|
117
|
+
let newValue = allowsVideoFrameAnalysis ?? true
|
|
118
|
+
|
|
119
|
+
view.playerViewController.allowsVideoFrameAnalysis = newValue
|
|
120
|
+
|
|
121
|
+
// Setting the `allowsVideoFrameAnalysis` to false after the scanning was already perofrmed doesn't update the UI.
|
|
122
|
+
// We can force the desired behaviour by quickly toggling the property. Setting it to true clears existing requests,
|
|
123
|
+
// which updates the UI, hiding the button, then setting it to false before it detects any text keeps it in the desired state.
|
|
124
|
+
// Tested in iOS 17.5
|
|
125
|
+
if !newValue {
|
|
126
|
+
view.playerViewController.allowsVideoFrameAnalysis = true
|
|
127
|
+
view.playerViewController.allowsVideoFrameAnalysis = false
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
#endif
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
AsyncFunction("enterFullscreen") { view in
|
|
134
|
+
view.enterFullscreen()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
AsyncFunction("exitFullscreen") { view in
|
|
138
|
+
view.exitFullscreen()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
AsyncFunction("startPictureInPicture") { view in
|
|
142
|
+
try view.startPictureInPicture()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
AsyncFunction("stopPictureInPicture") { view in
|
|
146
|
+
view.stopPictureInPicture()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
Class(VideoPlayer.self) {
|
|
151
|
+
Constructor { (source: VideoSource?, useSynchronousReplace: Bool?) -> VideoPlayer in
|
|
152
|
+
let useSynchronousReplace = useSynchronousReplace ?? false
|
|
153
|
+
let player = AVPlayer()
|
|
154
|
+
let videoPlayer = try VideoPlayer(player, initialSource: source, useSynchronousReplace: useSynchronousReplace)
|
|
155
|
+
player.pause()
|
|
156
|
+
return videoPlayer
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Property("playing") { player -> Bool in
|
|
160
|
+
return player.isPlaying
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Property("muted") { player -> Bool in
|
|
164
|
+
return player.isMuted
|
|
165
|
+
}
|
|
166
|
+
.set { (player, muted: Bool) in
|
|
167
|
+
player.isMuted = muted
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
Property("allowsExternalPlayback") { player -> Bool in
|
|
171
|
+
return player.ref.allowsExternalPlayback
|
|
172
|
+
}
|
|
173
|
+
.set { (player, allowsExternalPlayback: Bool) in
|
|
174
|
+
player.ref.allowsExternalPlayback = allowsExternalPlayback
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
Property("staysActiveInBackground") { player -> Bool in
|
|
178
|
+
return player.staysActiveInBackground
|
|
179
|
+
}
|
|
180
|
+
.set { (player, staysActive: Bool) in
|
|
181
|
+
player.staysActiveInBackground = staysActive
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
Property("loop") { player -> Bool in
|
|
185
|
+
return player.loop
|
|
186
|
+
}
|
|
187
|
+
.set { (player, loop: Bool) in
|
|
188
|
+
player.loop = loop
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Property("currentTime") { player -> Double in
|
|
192
|
+
return player.currentTime
|
|
193
|
+
}
|
|
194
|
+
.set { (player, time: Double) in
|
|
195
|
+
// Only clamp the lower limit, AVPlayer automatically clamps the upper limit.
|
|
196
|
+
player.currentTime = time
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
Property("currentLiveTimestamp") { player -> Double? in
|
|
200
|
+
return player.currentLiveTimestamp
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Property("currentOffsetFromLive") { player -> Double? in
|
|
204
|
+
return player.currentOffsetFromLive
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
Property("targetOffsetFromLive") { player -> Double in
|
|
208
|
+
return player.ref.currentItem?.configuredTimeOffsetFromLive.seconds ?? 0
|
|
209
|
+
}
|
|
210
|
+
.set { (player, timeOffset: Double) in
|
|
211
|
+
let timeOffset = CMTime(seconds: timeOffset, preferredTimescale: .max)
|
|
212
|
+
player.ref.currentItem?.configuredTimeOffsetFromLive = timeOffset
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
Property("duration") { player -> Double in
|
|
216
|
+
let duration = player.ref.currentItem?.duration.seconds ?? 0
|
|
217
|
+
return duration.isNaN ? 0 : duration
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
Property("playbackRate") { player -> Float in
|
|
221
|
+
return player.playbackRate
|
|
222
|
+
}
|
|
223
|
+
.set { (player, playbackRate: Float) in
|
|
224
|
+
player.playbackRate = playbackRate
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
Property("isLive") { player -> Bool in
|
|
228
|
+
return player.ref.currentItem?.duration.isIndefinite ?? false
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
Property("preservesPitch") { player -> Bool in
|
|
232
|
+
return player.preservesPitch
|
|
233
|
+
}
|
|
234
|
+
.set { (player, preservesPitch: Bool) in
|
|
235
|
+
player.preservesPitch = preservesPitch
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
Property("timeUpdateEventInterval") { player -> Double in
|
|
239
|
+
return player.timeUpdateEventInterval
|
|
240
|
+
}
|
|
241
|
+
.set { (player, timeUpdateEventInterval: Double) in
|
|
242
|
+
player.timeUpdateEventInterval = timeUpdateEventInterval
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
Property("showNowPlayingNotification") { player -> Bool in
|
|
246
|
+
return player.showNowPlayingNotification
|
|
247
|
+
}
|
|
248
|
+
.set { (player, showNowPlayingNotification: Bool) in
|
|
249
|
+
player.showNowPlayingNotification = showNowPlayingNotification
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Property("status") { player in
|
|
253
|
+
return player.status.rawValue
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
Property("volume") { player -> Float in
|
|
257
|
+
return player.volume
|
|
258
|
+
}
|
|
259
|
+
.set { (player, volume: Float) in
|
|
260
|
+
player.volume = volume
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
Property("bufferedPosition") { player -> Double in
|
|
264
|
+
return player.bufferedPosition
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
Property("bufferOptions") { player -> [String: Any] in
|
|
268
|
+
return player.bufferOptions.toDictionary()
|
|
269
|
+
}
|
|
270
|
+
.set { (player, bufferOptions: BufferOptions) in
|
|
271
|
+
player.bufferOptions = bufferOptions
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
Property("audioMixingMode") { player -> AudioMixingMode in
|
|
275
|
+
return player.audioMixingMode
|
|
276
|
+
}
|
|
277
|
+
.set { player, audioMixingMode in
|
|
278
|
+
player.audioMixingMode = audioMixingMode
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Property("availableVideoTracks") { player -> [VideoTrack] in
|
|
282
|
+
return player.availableVideoTracks
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
Property("videoTrack") { player -> VideoTrack? in
|
|
286
|
+
return player.currentVideoTrack
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
Property("availableSubtitleTracks") { player -> [SubtitleTrack] in
|
|
290
|
+
return player.subtitles.availableSubtitleTracks
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
Property("subtitleTrack") { player -> SubtitleTrack? in
|
|
294
|
+
return player.subtitles.currentSubtitleTrack
|
|
295
|
+
}
|
|
296
|
+
.set { player, subtitleTrack in
|
|
297
|
+
player.subtitles.selectSubtitleTrack(subtitleTrack: subtitleTrack)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
Property("availableAudioTracks") { player -> [AudioTrack] in
|
|
301
|
+
return player.audioTracks.availableAudioTracks
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
Property("audioTrack") { player -> AudioTrack? in
|
|
305
|
+
return player.audioTracks.currentAudioTrack
|
|
306
|
+
}
|
|
307
|
+
.set { player, audioTrack in
|
|
308
|
+
player.audioTracks.selectAudioTrack(audioTrack: audioTrack)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Function("play") { player in
|
|
312
|
+
player.ref.play()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
Function("pause") { player in
|
|
316
|
+
player.ref.pause()
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
Function("replace") { (player, source: Either<String, VideoSource>?) in
|
|
320
|
+
let videoSource = parseSource(source: source)
|
|
321
|
+
try player.replaceCurrentItem(with: videoSource)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
AsyncFunction("replaceAsync") { (player, source: Either<String, VideoSource>?) in
|
|
325
|
+
let videoSource = parseSource(source: source)
|
|
326
|
+
try await player.replaceCurrentItem(with: videoSource)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
Function("seekBy") { (player, seconds: Double) in
|
|
330
|
+
let newTime = player.ref.currentTime() + CMTime(seconds: seconds, preferredTimescale: .max)
|
|
331
|
+
|
|
332
|
+
player.ref.seek(to: newTime)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
Function("replay") { player in
|
|
336
|
+
player.ref.seek(to: CMTime.zero)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
AsyncFunction("generateThumbnailsAsync") { (player: VideoPlayer, times: [CMTime]?, options: VideoThumbnailOptions?) -> [VideoThumbnail] in
|
|
340
|
+
guard let times, !times.isEmpty else {
|
|
341
|
+
return []
|
|
342
|
+
}
|
|
343
|
+
guard let asset = player.ref.currentItem?.asset else {
|
|
344
|
+
// TODO: We should throw here as nothing is playing
|
|
345
|
+
return []
|
|
346
|
+
}
|
|
347
|
+
return try await generateThumbnails(
|
|
348
|
+
asset: asset,
|
|
349
|
+
times: times,
|
|
350
|
+
options: options ?? .default
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
Class(VideoThumbnail.self) {
|
|
356
|
+
Property("width", \.ref.size.width)
|
|
357
|
+
Property("height", \.ref.size.height)
|
|
358
|
+
Property("requestedTime", \.requestedTime.seconds)
|
|
359
|
+
Property("actualTime", \.actualTime.seconds)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
OnAppEntersBackground {
|
|
363
|
+
VideoManager.shared.onAppBackgrounded()
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
OnAppEntersForeground {
|
|
367
|
+
VideoManager.shared.onAppForegrounded()
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private func parseSource(source: Either<String, VideoSource>?) -> VideoSource? {
|
|
372
|
+
guard let source else {
|
|
373
|
+
return nil
|
|
374
|
+
}
|
|
375
|
+
if source.is(String.self), let url: String = source.get() {
|
|
376
|
+
return VideoSource(uri: URL(string: url))
|
|
377
|
+
}
|
|
378
|
+
if source.is(VideoSource.self) {
|
|
379
|
+
return source.get()
|
|
380
|
+
}
|
|
381
|
+
return nil
|
|
382
|
+
}
|
|
383
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Constains player properties that have been set while the current item was being loaded and which can be
|
|
2
|
+
// correctly applied only after the player has set it's item. For now it's only currentTime, but we may add more in the future
|
|
3
|
+
internal class DangerousPropertiesStore {
|
|
4
|
+
var ownerIsReplacing: Bool = false
|
|
5
|
+
var currentTime: Double?
|
|
6
|
+
|
|
7
|
+
func applyProperties(to player: VideoPlayer, reset shouldReset: Bool = true) {
|
|
8
|
+
if let currentTime {
|
|
9
|
+
player.currentTime = currentTime
|
|
10
|
+
}
|
|
11
|
+
if shouldReset {
|
|
12
|
+
reset()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func reset() {
|
|
17
|
+
currentTime = nil
|
|
18
|
+
}
|
|
19
|
+
}
|