rn-videofeed 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.
@@ -0,0 +1,131 @@
1
+ //
2
+ // VideoFeedViewManager.swift
3
+ // App
4
+ //
5
+ // Created by Venkatesh Mandapati on 05/06/2025.
6
+ //
7
+
8
+ import Foundation
9
+ import React
10
+ import UIKit
11
+
12
+ @objc(VideoFeedViewManager)
13
+ class VideoFeedViewManager: RCTViewManager {
14
+
15
+ override func view() -> UIView! {
16
+ let view = VideoFeedView()
17
+
18
+ // Connect the event emitter
19
+ if let eventEmitter = bridge?.module(forName: "VideoFeedEventEmitter") as? VideoFeedEventEmitter {
20
+ view.eventEmitter = eventEmitter
21
+ print("🔥 Connected VideoFeedEventEmitter to VideoFeedView")
22
+ } else {
23
+ print("⚠️ Failed to connect VideoFeedEventEmitter")
24
+ }
25
+
26
+ // Set up event handlers
27
+ view.onEndReached = { [weak self] in
28
+ self?.bridge?.eventDispatcher()?.sendAppEvent(withName: "onEndReached", body: nil)
29
+ }
30
+ view.onVideoChange = { videoId in
31
+ self.bridge?.eventDispatcher()?.sendAppEvent(withName: "onVideoChange", body: ["videoId": videoId])
32
+ }
33
+ return view
34
+ }
35
+
36
+ override static func requiresMainQueueSetup() -> Bool {
37
+ return true
38
+ }
39
+
40
+ // Common function to safely access VideoFeedView using the working approach
41
+ private func withVideoFeedView(_ reactTag: NSNumber, operation: String, action: @escaping (VideoFeedView) -> Void) {
42
+
43
+ // Add a small delay to ensure view is properly registered
44
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
45
+ guard let uiManager = self?.bridge?.uiManager else {
46
+ return
47
+ }
48
+
49
+ // Use the modern approach for New Architecture
50
+ uiManager.addUIBlock { _, viewRegistry in
51
+
52
+ // Try to get the view from the registry
53
+ if let view = viewRegistry?[reactTag] as? VideoFeedView {
54
+ action(view)
55
+ } else {
56
+
57
+ // Alternative approach: try to get view directly
58
+ if let directView = uiManager.view(forReactTag: reactTag) as? VideoFeedView {
59
+ action(directView)
60
+ } else {
61
+
62
+ // Last resort: try to enumerate all views
63
+ for (tag, view) in viewRegistry ?? [:] {
64
+ if let videoView = view as? VideoFeedView {
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ @objc func setVideos(_ reactTag: NSNumber, videos: [[String: Any]]) {
74
+ withVideoFeedView(reactTag, operation: "Setting videos") { view in
75
+ view.setVideos(videos)
76
+ }
77
+ }
78
+
79
+ @objc func appendVideos(_ reactTag: NSNumber, videos: [[String: Any]]) {
80
+ withVideoFeedView(reactTag, operation: "Appending videos") { view in
81
+ view.appendVideos(videos)
82
+ }
83
+ }
84
+
85
+ @objc func setFeedActive(_ reactTag: NSNumber, isActive: Bool) {
86
+ withVideoFeedView(reactTag, operation: "Setting feed active") { view in
87
+ view.setFeedActive(isActive)
88
+ }
89
+ }
90
+
91
+ @objc func pauseVideo(_ reactTag: NSNumber) {
92
+ withVideoFeedView(reactTag, operation: "Pause video") { view in
93
+ view.pauseCurrentVideo()
94
+ }
95
+ }
96
+
97
+ @objc func playVideo(_ reactTag: NSNumber) {
98
+ withVideoFeedView(reactTag, operation: "Play video") { view in
99
+ view.playCurrentVideo()
100
+ }
101
+ }
102
+
103
+ @objc func togglePlayPause(_ reactTag: NSNumber) {
104
+ withVideoFeedView(reactTag, operation: "Toggle play/pause") { view in
105
+ let isNowPlaying = view.togglePlayPause()
106
+ // Emit event back to React Native
107
+ self.bridge?.eventDispatcher()?.sendAppEvent(
108
+ withName: "onPlayStateChanged",
109
+ body: ["isPlaying": isNowPlaying]
110
+ )
111
+ }
112
+ }
113
+
114
+ @objc func isVideoPlaying(_ reactTag: NSNumber) {
115
+ withVideoFeedView(reactTag, operation: "Check playing state") { view in
116
+ let isPlaying = view.isCurrentVideoPlaying()
117
+ self.bridge?.eventDispatcher()?.sendAppEvent(
118
+ withName: "onPlayStateChecked",
119
+ body: ["isPlaying": isPlaying]
120
+ )
121
+ }
122
+ }
123
+
124
+ @objc func addEventListener(_ eventName: String) {
125
+ // No-op: Handled by RCTEventEmitter
126
+ }
127
+
128
+ @objc func removeEventListener(_ eventName: String) {
129
+ // No-op: Handled by RCTEventEmitter
130
+ }
131
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "rn-videofeed",
3
+ "version": "0.1.0",
4
+ "description": "Native vertical full-screen video feed for React Native (Reels/TikTok style) with seamless resume.",
5
+ "main": "src/index.ts",
6
+ "types": "index.d.ts",
7
+ "react-native": "src/index.ts",
8
+ "author": "Venkatesh Mandapati",
9
+ "license": "MIT",
10
+ "homepage": "https://github.com/venky145/RN-VideoFeed#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/venky145/RN-VideoFeed.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/venky145/RN-VideoFeed/issues"
17
+ },
18
+ "files": [
19
+ "src",
20
+ "android/src",
21
+ "android/build.gradle",
22
+ "ios",
23
+ "index.d.ts",
24
+ "RNVideoFeed.podspec",
25
+ "react-native.config.js",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "dependencies": {
30
+ "@babel/runtime": "^7.25.0"
31
+ },
32
+ "peerDependencies": {
33
+ "react": ">=18.0.0",
34
+ "react-native": ">=0.76.0"
35
+ },
36
+ "keywords": ["react-native", "video", "feed"]
37
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ dependency: {},
3
+ }
@@ -0,0 +1,24 @@
1
+ import { NativeEventEmitter, NativeModules } from 'react-native'
2
+
3
+ import type { FeedPlayerNativeProps } from './VideoFeedView.native'
4
+ import type { NativeModule } from 'react-native'
5
+
6
+ interface VideoFeedManager extends NativeModule {
7
+ setVideos: (reactTag: number, videos: FeedPlayerNativeProps[]) => void
8
+ appendVideos: (reactTag: number, videos: FeedPlayerNativeProps[]) => void
9
+ setFeedActive: (reactTag: number, isActive: boolean) => void
10
+ addEventListener: (eventName: string) => void
11
+ removeEventListener: (eventName: string) => void
12
+ pauseVideo: (reactTag: number) => void
13
+ playVideo: (reactTag: number) => void
14
+ togglePlayPause: (reactTag: number) => void
15
+ isVideoPlaying: (reactTag: number) => void
16
+ }
17
+
18
+ const VideoFeedManagerNative = NativeModules.VideoFeedViewManager as VideoFeedManager
19
+
20
+ const VideoFeedEventEmitterNative = (NativeModules.VideoFeedEventEmitter ||
21
+ VideoFeedManagerNative) as VideoFeedManager
22
+ const VideoFeedEmitter = new NativeEventEmitter(VideoFeedEventEmitterNative)
23
+
24
+ export { VideoFeedManagerNative, VideoFeedEmitter, type VideoFeedManager }
@@ -0,0 +1,17 @@
1
+ import { requireNativeComponent } from 'react-native'
2
+
3
+ import type { LayoutChangeEvent, ViewStyle } from 'react-native'
4
+
5
+ export type FeedPlayerNativeProps = {
6
+ id?: string
7
+ videoUrl?: string
8
+ thumbnailUrl?: string
9
+ createdAt?: boolean
10
+ viewCount?: number
11
+ style?: ViewStyle
12
+ onLayout?: (event: LayoutChangeEvent) => void
13
+ }
14
+
15
+ const VideoFeedView = requireNativeComponent<FeedPlayerNativeProps>('VideoFeedView')
16
+
17
+ export default VideoFeedView
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { default, default as VideoFeedView } from './VideoFeedView.native'
2
+ export { VideoFeedManagerNative, VideoFeedEmitter, type VideoFeedManager } from './VideoFeedManagerBridge'
3
+ export type { FeedPlayerNativeProps } from './VideoFeedView.native'