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.
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/RNVideoFeed.podspec +25 -0
- package/android/build.gradle +43 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/rnvideofeed/FeedPlayer.kt +202 -0
- package/android/src/main/java/com/rnvideofeed/VideoData.kt +8 -0
- package/android/src/main/java/com/rnvideofeed/VideoFeedCell.kt +185 -0
- package/android/src/main/java/com/rnvideofeed/VideoFeedModule.kt +176 -0
- package/android/src/main/java/com/rnvideofeed/VideoFeedPackage.kt +17 -0
- package/android/src/main/java/com/rnvideofeed/VideoFeedPlayerPool.kt +78 -0
- package/android/src/main/java/com/rnvideofeed/VideoFeedView.kt +706 -0
- package/android/src/main/java/com/rnvideofeed/VideoFeedViewManager.kt +129 -0
- package/android/src/main/res/xml/player_view_texture.xml +5 -0
- package/index.d.ts +30 -0
- package/ios/FeedOptions/ContentCardInfoView.swift +312 -0
- package/ios/FeedPlayer.swift +174 -0
- package/ios/VideoFeedCell.swift +117 -0
- package/ios/VideoFeedEventEmitter.swift +24 -0
- package/ios/VideoFeedManagerBridge.h +17 -0
- package/ios/VideoFeedManagerBridge.m +31 -0
- package/ios/VideoFeedView.swift +432 -0
- package/ios/VideoFeedViewManager.swift +131 -0
- package/package.json +37 -0
- package/react-native.config.js +3 -0
- package/src/VideoFeedManagerBridge.ts +24 -0
- package/src/VideoFeedView.native.tsx +17 -0
- package/src/index.ts +3 -0
|
@@ -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,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