expo-libvlc-player 0.1.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 (70) hide show
  1. package/.eslintrc.js +2 -0
  2. package/README.md +107 -0
  3. package/android/.gradle/9.0-milestone-1/checksums/checksums.lock +0 -0
  4. package/android/.gradle/9.0-milestone-1/fileChanges/last-build.bin +0 -0
  5. package/android/.gradle/9.0-milestone-1/fileHashes/fileHashes.lock +0 -0
  6. package/android/.gradle/9.0-milestone-1/gc.properties +0 -0
  7. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  8. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  9. package/android/.gradle/config.properties +2 -0
  10. package/android/.gradle/ideaInitScripts/ijtgtmapper.gradle +3 -0
  11. package/android/.gradle/vcs-1/gc.properties +0 -0
  12. package/android/.idea/AndroidProjectSystem.xml +6 -0
  13. package/android/.idea/android.iml +9 -0
  14. package/android/.idea/caches/deviceStreaming.xml +835 -0
  15. package/android/.idea/deviceManager.xml +13 -0
  16. package/android/.idea/gradle.xml +13 -0
  17. package/android/.idea/misc.xml +3 -0
  18. package/android/.idea/modules.xml +8 -0
  19. package/android/.idea/runConfigurations.xml +17 -0
  20. package/android/.idea/vcs.xml +6 -0
  21. package/android/.idea/workspace.xml +72 -0
  22. package/android/build.gradle +53 -0
  23. package/android/local.properties +8 -0
  24. package/android/proguard-rules.pro +19 -0
  25. package/android/src/main/AndroidManifest.xml +4 -0
  26. package/android/src/main/java/expo/modules/libvlcplayer/AudioFocusManager.kt +210 -0
  27. package/android/src/main/java/expo/modules/libvlcplayer/VlcPlayerManager.kt +82 -0
  28. package/android/src/main/java/expo/modules/libvlcplayer/VlcPlayerModule.kt +129 -0
  29. package/android/src/main/java/expo/modules/libvlcplayer/VlcPlayerView.kt +328 -0
  30. package/android/src/main/java/expo/modules/libvlcplayer/enums/AudioMixingMode.kt +20 -0
  31. package/app.plugin.js +1 -0
  32. package/build/VlcPlayer.types.d.ts +278 -0
  33. package/build/VlcPlayer.types.d.ts.map +1 -0
  34. package/build/VlcPlayer.types.js +2 -0
  35. package/build/VlcPlayer.types.js.map +1 -0
  36. package/build/VlcPlayerModule.d.ts +6 -0
  37. package/build/VlcPlayerModule.d.ts.map +1 -0
  38. package/build/VlcPlayerModule.js +4 -0
  39. package/build/VlcPlayerModule.js.map +1 -0
  40. package/build/VlcPlayerView.d.ts +5 -0
  41. package/build/VlcPlayerView.d.ts.map +1 -0
  42. package/build/VlcPlayerView.js +36 -0
  43. package/build/VlcPlayerView.js.map +1 -0
  44. package/build/index.d.ts +4 -0
  45. package/build/index.d.ts.map +1 -0
  46. package/build/index.js +5 -0
  47. package/build/index.js.map +1 -0
  48. package/build/utils/props.d.ts +3 -0
  49. package/build/utils/props.d.ts.map +1 -0
  50. package/build/utils/props.js +11 -0
  51. package/build/utils/props.js.map +1 -0
  52. package/eslint.config.js +50 -0
  53. package/expo-module.config.json +16 -0
  54. package/ios/Enums/AudioMixingMode.swift +35 -0
  55. package/ios/ExpoLibVlcPlayer.podspec +29 -0
  56. package/ios/VlcPlayerManager.swift +116 -0
  57. package/ios/VlcPlayerModule.swift +110 -0
  58. package/ios/VlcPlayerView.swift +321 -0
  59. package/package.json +49 -0
  60. package/plugin/build/withExpoLibVlcPlayer.d.ts +3 -0
  61. package/plugin/build/withExpoLibVlcPlayer.js +18 -0
  62. package/plugin/src/withExpoLibVlcPlayer.ts +21 -0
  63. package/plugin/tsconfig.json +9 -0
  64. package/plugin/tsconfig.tsbuildinfo +1 -0
  65. package/src/VlcPlayer.types.ts +291 -0
  66. package/src/VlcPlayerModule.ts +7 -0
  67. package/src/VlcPlayerView.tsx +73 -0
  68. package/src/index.ts +4 -0
  69. package/src/utils/props.ts +20 -0
  70. package/tsconfig.json +9 -0
@@ -0,0 +1,321 @@
1
+ import AVFoundation
2
+ import ExpoModulesCore
3
+ import MobileVLCKit
4
+
5
+ let defaultPlayerRate: Float = 1.0
6
+ let minPlayerVolume: Int = 0
7
+ let maxPlayerVolume: Int = 100
8
+ let playerVolumeStep: Int = 10
9
+
10
+ private let useTextureViews = false
11
+ private let enableSubtitles = true
12
+
13
+ class VlcPlayerView: ExpoView, VLCMediaPlayerDelegate {
14
+ var mediaPlayer: VLCMediaPlayer?
15
+
16
+ private var uri: String = ""
17
+ private var options: [String] = []
18
+ private var previousVolume: Int = maxPlayerVolume
19
+ private var shouldRepeat: Bool = false
20
+ var audioMixingMode: AudioMixingMode = .auto
21
+ var playInBackground: Bool = false
22
+ private var autoplay: Bool = true
23
+
24
+ private let onBuffering = EventDispatcher()
25
+ private let onPlaying = EventDispatcher()
26
+ private let onPaused = EventDispatcher()
27
+ private let onStopped = EventDispatcher()
28
+ private let onEnded = EventDispatcher()
29
+ private let onRepeat = EventDispatcher()
30
+ private let onWarn = EventDispatcher()
31
+ private let onError = EventDispatcher()
32
+ private let onPositionChanged = EventDispatcher()
33
+ private let onLoad = EventDispatcher()
34
+ let onBackground = EventDispatcher()
35
+
36
+ required init(appContext: AppContext? = nil) {
37
+ super.init(appContext: appContext)
38
+
39
+ backgroundColor = UIColor.black
40
+
41
+ VlcPlayerManager.shared.registerView(view: self)
42
+
43
+ clipsToBounds = true
44
+ }
45
+
46
+ func mediaPlayerStateChanged(_: Notification) {
47
+ guard let player = mediaPlayer else { return }
48
+
49
+ switch player.state {
50
+ case .buffering:
51
+ onBuffering([:])
52
+
53
+ var audioTracks: [[String: Any]] = []
54
+
55
+ if let audios = player.audioTrackNames as? [String] {
56
+ if let audioIndexes = player.audioTrackIndexes as? [NSNumber] {
57
+ for (index, name) in audios.enumerated() {
58
+ let trackId = audioIndexes[index].intValue
59
+ if trackId != -1 && name != "Disable" {
60
+ audioTracks.append([
61
+ "id": trackId,
62
+ "name": name,
63
+ ])
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ var subtitleTracks: [[String: Any]] = []
70
+
71
+ if let subtitles = player.videoSubTitlesNames as? [String] {
72
+ if let subtitleIndexes = player.videoSubTitlesIndexes as? [NSNumber] {
73
+ for (index, name) in subtitles.enumerated() {
74
+ let trackId = subtitleIndexes[index].intValue
75
+ subtitleTracks.append([
76
+ "id": trackId,
77
+ "name": name,
78
+ ])
79
+ }
80
+ }
81
+ }
82
+
83
+ let video = player.videoSize
84
+ let ratio = player.videoAspectRatio
85
+ var length = 0
86
+ if let media = player.media {
87
+ length = Int(media.length.intValue)
88
+ }
89
+ let tracks = [
90
+ "audio": audioTracks,
91
+ "subtitle": subtitleTracks,
92
+ ]
93
+ let seekable = player.isSeekable
94
+
95
+ let videoInfo: [String: Any] = [
96
+ "width": Int(video.width),
97
+ "height": Int(video.height),
98
+ "aspectRatio": ratio,
99
+ "duration": Double(length),
100
+ "tracks": tracks,
101
+ "seekable": seekable,
102
+ ]
103
+
104
+ onLoad(videoInfo)
105
+ case .playing:
106
+ onPlaying([:])
107
+ VlcPlayerManager.shared.setAppropriateAudioSessionOrWarn()
108
+ case .paused:
109
+ onPaused([:])
110
+ VlcPlayerManager.shared.setAppropriateAudioSessionOrWarn()
111
+ case .stopped:
112
+ onStopped([:])
113
+ VlcPlayerManager.shared.setAppropriateAudioSessionOrWarn()
114
+
115
+ let position = 0.0
116
+ onPositionChanged(["position": position])
117
+ case .ended:
118
+ onEnded([:])
119
+
120
+ let manualRepeat = !options.hasRepeatOptions() && shouldRepeat
121
+
122
+ if manualRepeat {
123
+ player.stop()
124
+ player.play()
125
+ onRepeat([:])
126
+ }
127
+ case .error:
128
+ let error = ["error": "Player encountered an error"]
129
+ onError(error)
130
+ default:
131
+ break
132
+ }
133
+ }
134
+
135
+ func mediaPlayerPositionChanged(_: Notification) {
136
+ guard let player = mediaPlayer else { return }
137
+
138
+ let position = ["position": player.position]
139
+ onPositionChanged(position)
140
+ }
141
+
142
+ func createPlayer() {
143
+ destroyPlayer()
144
+
145
+ mediaPlayer = VLCMediaPlayer(options: options)
146
+
147
+ if let player = mediaPlayer {
148
+ player.delegate = self
149
+ player.drawable = self
150
+
151
+ guard let url = URL(string: uri) else {
152
+ let error = ["error": "Invalid URI, media could not be set"]
153
+ onError(error)
154
+ return
155
+ }
156
+
157
+ player.media = VLCMedia(url: url)
158
+
159
+ if autoplay {
160
+ player.play()
161
+ }
162
+ }
163
+ }
164
+
165
+ func destroyPlayer() {
166
+ if let player = mediaPlayer {
167
+ player.stop()
168
+ mediaPlayer = nil
169
+ }
170
+ }
171
+
172
+ func setUri(_ uri: String) {
173
+ let old = self.uri
174
+ self.uri = uri
175
+
176
+ if uri != old {
177
+ createPlayer()
178
+ }
179
+ }
180
+
181
+ func setSubtitle(_ subtitle: [String: Any]?) {
182
+ guard let player = mediaPlayer,
183
+ let subtitle = subtitle,
184
+ !subtitle.isEmpty else { return }
185
+
186
+ let uri = subtitle["uri"] as? String ?? ""
187
+
188
+ guard let url = URL(string: uri) else {
189
+ let error = ["error": "Invalid URI, subtitle could not be set"]
190
+ onError(error)
191
+ return
192
+ }
193
+
194
+ let enable = subtitle["enable"] as? Bool ?? enableSubtitles
195
+
196
+ player.addPlaybackSlave(url, type: .subtitle, enforce: enable)
197
+ }
198
+
199
+ func setOptions(_ options: [String]) {
200
+ let old = self.options
201
+ self.options = options
202
+
203
+ if options != old {
204
+ createPlayer()
205
+ }
206
+ }
207
+
208
+ func setVolume(_ volume: Int) {
209
+ guard let player = mediaPlayer else { return }
210
+
211
+ let newVolume = max(minPlayerVolume, min(maxPlayerVolume, volume))
212
+ previousVolume = newVolume
213
+
214
+ player.audio?.volume = Int32(newVolume)
215
+ VlcPlayerManager.shared.setAppropriateAudioSessionOrWarn()
216
+ }
217
+
218
+ func setMute(_ mute: Bool) {
219
+ guard let player = mediaPlayer else { return }
220
+
221
+ let newVolume = !mute ?
222
+ max(playerVolumeStep, min(maxPlayerVolume, previousVolume)) :
223
+ minPlayerVolume
224
+
225
+ player.audio?.volume = Int32(newVolume)
226
+ VlcPlayerManager.shared.setAppropriateAudioSessionOrWarn()
227
+ }
228
+
229
+ func setRate(_ rate: Float) {
230
+ guard let player = mediaPlayer else { return }
231
+
232
+ player.rate = rate
233
+ }
234
+
235
+ func setTracks(_ tracks: [String: Any]?) {
236
+ guard let player = mediaPlayer,
237
+ let tracks = tracks,
238
+ !tracks.isEmpty else { return }
239
+
240
+ let audioTrack = tracks["audio"] as? Int ?? -1
241
+ let subtitleTrack = tracks["subtitle"] as? Int ?? -1
242
+
243
+ player.currentAudioTrackIndex = Int32(audioTrack)
244
+ player.currentVideoSubTitleIndex = Int32(subtitleTrack)
245
+ }
246
+
247
+ func setRepeat(_ shouldRepeat: Bool) {
248
+ if shouldRepeat && options.hasRepeatOptions() {
249
+ let warn = ["warn": "Repeat already enabled in options"]
250
+ return onWarn(warn)
251
+ }
252
+
253
+ self.shouldRepeat = shouldRepeat
254
+ }
255
+
256
+ func setAspectRatio(_ aspectRatio: String?) {
257
+ guard let player = mediaPlayer,
258
+ let aspectRatio = aspectRatio else { return }
259
+
260
+ if let cString = strdup(aspectRatio) {
261
+ player.videoAspectRatio = cString
262
+ free(cString)
263
+ }
264
+ }
265
+
266
+ func setAudioMixingMode(_ audioMixingMode: AudioMixingMode) {
267
+ self.audioMixingMode = audioMixingMode
268
+ VlcPlayerManager.shared.setAppropriateAudioSessionOrWarn()
269
+ }
270
+
271
+ func setPlayInBackground(_ playInBackground: Bool) {
272
+ self.playInBackground = playInBackground
273
+ VlcPlayerManager.shared.setAppropriateAudioSessionOrWarn()
274
+ }
275
+
276
+ func setAutoplay(_ autoplay: Bool) {
277
+ self.autoplay = autoplay
278
+ }
279
+
280
+ func play() {
281
+ mediaPlayer?.play()
282
+ }
283
+
284
+ func pause() {
285
+ mediaPlayer?.pause()
286
+ }
287
+
288
+ func stop() {
289
+ mediaPlayer?.stop()
290
+ }
291
+
292
+ func seek(_ position: Float) {
293
+ guard let player = mediaPlayer else { return }
294
+
295
+ if player.isSeekable {
296
+ player.position = position
297
+ } else {
298
+ let error = ["error": "Media is not seekable"]
299
+ onError(error)
300
+ }
301
+ }
302
+
303
+ deinit {
304
+ VlcPlayerManager.shared.unregisterView(view: self)
305
+ destroyPlayer()
306
+ }
307
+ }
308
+
309
+ private extension Array where Element == String {
310
+ func hasRepeatOptions() -> Bool {
311
+ let prefixes: Set<String> = [
312
+ "--input-repeat=", "-input-repeat=", ":input-repeat=",
313
+ ]
314
+
315
+ return contains { arg in
316
+ prefixes.contains { prefix in
317
+ arg.hasPrefix(prefix)
318
+ }
319
+ }
320
+ }
321
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "expo-libvlc-player",
3
+ "version": "0.1.0",
4
+ "description": "LibVLC Player for Expo",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "scripts": {
8
+ "build": "expo-module build",
9
+ "clean": "expo-module clean",
10
+ "lint": "expo-module lint",
11
+ "test": "expo-module test",
12
+ "prepare": "expo-module prepare",
13
+ "prepublishOnly": "expo-module prepublishOnly",
14
+ "expo-module": "expo-module",
15
+ "open:ios": "xed example/ios",
16
+ "open:android": "open -a \"Android Studio\" example/android"
17
+ },
18
+ "keywords": [
19
+ "react-native",
20
+ "expo",
21
+ "libvlc",
22
+ "player",
23
+ "expo-libvlc-player",
24
+ "ExpoLibVlcPlayer"
25
+ ],
26
+ "repository": "https://github.com/cornejobarraza/expo-libvlc-player",
27
+ "bugs": {
28
+ "url": "https://github.com/cornejobarraza/expo-libvlc-player/issues"
29
+ },
30
+ "author": "David Cornejo <davidcornejo1@live.com> (https://github.com/cornejobarraza)",
31
+ "license": "MIT",
32
+ "homepage": "https://github.com/cornejobarraza/expo-libvlc-player#readme",
33
+ "devDependencies": {
34
+ "@eslint/compat": "^1.3.1",
35
+ "@eslint/eslintrc": "^3.3.1",
36
+ "@eslint/js": "^9.30.0",
37
+ "@types/react": "~19.0.0",
38
+ "eslint": "^9.30.0",
39
+ "expo": "53.0.16",
40
+ "expo-module-scripts": "^4.1.6",
41
+ "globals": "^16.2.0",
42
+ "react-native": "0.79.5"
43
+ },
44
+ "peerDependencies": {
45
+ "expo": "*",
46
+ "react": "*",
47
+ "react-native": "*"
48
+ }
49
+ }
@@ -0,0 +1,3 @@
1
+ import { type ConfigPlugin } from "expo/config-plugins";
2
+ declare const withExpoLibVlcPlayer: ConfigPlugin<{}>;
3
+ export default withExpoLibVlcPlayer;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("expo/config-plugins");
4
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
5
+ const withExpoLibVlcPlayer = (config) => {
6
+ (0, config_plugins_1.withInfoPlist)(config, (config) => {
7
+ const currentBackgroundModes = config.modResults.UIBackgroundModes ?? [];
8
+ if (!currentBackgroundModes.includes("audio")) {
9
+ config.modResults.UIBackgroundModes = [
10
+ ...currentBackgroundModes,
11
+ "audio",
12
+ ];
13
+ }
14
+ return config;
15
+ });
16
+ return config;
17
+ };
18
+ exports.default = withExpoLibVlcPlayer;
@@ -0,0 +1,21 @@
1
+ import { type ConfigPlugin, withInfoPlist } from "expo/config-plugins";
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
4
+ const withExpoLibVlcPlayer: ConfigPlugin<{}> = (config) => {
5
+ withInfoPlist(config, (config) => {
6
+ const currentBackgroundModes = config.modResults.UIBackgroundModes ?? [];
7
+
8
+ if (!currentBackgroundModes.includes("audio")) {
9
+ config.modResults.UIBackgroundModes = [
10
+ ...currentBackgroundModes,
11
+ "audio",
12
+ ];
13
+ }
14
+
15
+ return config;
16
+ });
17
+
18
+ return config;
19
+ };
20
+
21
+ export default withExpoLibVlcPlayer;
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "expo-module-scripts/tsconfig.plugin",
3
+ "compilerOptions": {
4
+ "outDir": "build",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["./src"],
8
+ "exclude": ["**/__mocks__/*", "**/__tests__/*"]
9
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/withExpoLibVlcPlayer.ts"],"version":"5.8.3"}