├── .babelrc ├── .buckconfig ├── .editorconfig ├── .env ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .watchmanconfig ├── App.js ├── README.md ├── Root.js ├── android ├── app │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── rnstreamer │ │ ├── MainActivity.java │ │ └── MainApplication.java ├── build.gradle ├── gradle.properties └── settings.gradle ├── app.json ├── index.js ├── ios └── Podfile ├── package.json ├── server ├── .env ├── index.js └── package.json └── src ├── components └── CommentList.js └── screens ├── Index.js ├── PublishStream.js └── ViewStream.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["module:metro-react-native-babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = false 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | quote_type = double 9 | 10 | [*.js] 11 | indent_style = space 12 | indent_size = 2 -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | MUX_TOKEN_ID="YOUR MUX TOKEN ID" 2 | MUX_TOKEN_SECRET="YOUR MUX TOKEN SECRET" 3 | 4 | CHANNELS_KEY="YOUR PUSHER CHANNELS APP INSTANCE KEY" 5 | CHANNELS_CLUSTER="YOUR PUSHER CHANNELS APP INSTANCE CLUSTER" -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | 16 | ; Ignore polyfills 17 | .*/Libraries/polyfills/.* 18 | 19 | ; Ignore metro 20 | .*/node_modules/metro/.* 21 | 22 | [include] 23 | 24 | [libs] 25 | node_modules/react-native/Libraries/react-native/react-native-interface.js 26 | node_modules/react-native/flow/ 27 | node_modules/react-native/flow-github/ 28 | 29 | [options] 30 | emoji=true 31 | 32 | esproposal.optional_chaining=enable 33 | esproposal.nullish_coalescing=enable 34 | 35 | module.system=haste 36 | module.system.haste.use_name_reducers=true 37 | # get basename 38 | module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' 39 | # strip .js or .js.flow suffix 40 | module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' 41 | # strip .ios suffix 42 | module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' 43 | module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' 44 | module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' 45 | module.system.haste.paths.blacklist=.*/__tests__/.* 46 | module.system.haste.paths.blacklist=.*/__mocks__/.* 47 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* 48 | module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* 49 | 50 | munge_underscores=true 51 | 52 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 53 | 54 | module.file_ext=.js 55 | module.file_ext=.jsx 56 | module.file_ext=.json 57 | module.file_ext=.native.js 58 | 59 | suppress_type=$FlowIssue 60 | suppress_type=$FlowFixMe 61 | suppress_type=$FlowFixMeProps 62 | suppress_type=$FlowFixMeState 63 | 64 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 65 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 66 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 67 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 68 | 69 | [version] 70 | ^0.78.0 71 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SafeAreaView, View, StyleSheet } from 'react-native'; 3 | 4 | import Root from "./Root"; 5 | 6 | const App = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | // 15 | 16 | const styles = StyleSheet.create({ 17 | container: { 18 | flex: 1 19 | } 20 | }); 21 | 22 | export default App; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-Native-Mux-Stream 2 | 3 | A live video streaming app created with React Native and Mux. 4 | 5 | Full tutorial is available at: [http://pusher.com/tutorials/video-streaming-app-mux-react-native](http://pusher.com/tutorials/video-streaming-app-mux-react-native) 6 | 7 | ### Prerequisites 8 | 9 | - React Native development environment 10 | - [Node.js](https://nodejs.org/en/) 11 | - [Yarn](https://yarnpkg.com/en/) 12 | - [Mux Account](https://mux.com/) 13 | - [Channels app instance](https://pusher.com/channels) 14 | - [ngrok account](https://ngrok.com/) 15 | 16 | ## Getting Started 17 | 18 | 1. Clone the repo: 19 | 20 | ``` 21 | git clone https://github.com/anchetaWern/React-Native-Mux-Stream.git 22 | cd RNStream 23 | ``` 24 | 25 | 2. Install the app dependencies: 26 | 27 | ``` 28 | yarn 29 | ``` 30 | 31 | 3. Eject the project (re-creates the `ios` and `android` folders): 32 | 33 | ``` 34 | react-native eject 35 | ``` 36 | 37 | 4. Link the packages: 38 | 39 | ``` 40 | react-native link @react-native-community/async-storage 41 | react-native link @react-native-community/netinfo 42 | react-native link react-native-config 43 | react-native link react-native-gesture-handler 44 | react-native link react-native-nodemediaclient 45 | react-native link react-native-permissions 46 | react-native link react-native-video 47 | ``` 48 | 49 | 5. Put additional config on `android/app/build.gradle` file: 50 | 51 | ``` 52 | apply plugin: "com.android.application" 53 | apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" // add this 54 | ``` 55 | 56 | And `android/build.gradle` file: 57 | 58 | ``` 59 | allprojects { 60 | repositories { 61 | mavenLocal() 62 | google() 63 | jcenter() 64 | maven { 65 | url "$rootDir/../node_modules/react-native/android" 66 | } 67 | 68 | // add this: 69 | maven { 70 | url 'https://jitpack.io' 71 | } 72 | } 73 | } 74 | ``` 75 | 76 | 6. Add permissions to `android/app/src/main/AndroidManifest.xml` file: 77 | 78 | ``` 79 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ... 95 | 96 | 97 | ``` 98 | 99 | 7. Update the `.env` file with your Mux and Channels app credentials, and the `server/.env` file with your Channels app credentials. 100 | 101 | 8. Set up the server: 102 | 103 | ``` 104 | cd server 105 | yarn 106 | ``` 107 | 108 | 9. Run the server: 109 | 110 | ``` 111 | yarn start 112 | ``` 113 | 114 | 10. Run ngrok: 115 | 116 | ``` 117 | ./ngrok http 5000 118 | ``` 119 | 120 | 11. Update the `src/screens/Index.js` file with your ngrok HTTPS URL. 121 | 122 | 12. Run the app: 123 | 124 | ``` 125 | react-native run-android 126 | react-native run-ios 127 | ``` 128 | 129 | ## Built With 130 | 131 | - [React Native](http://facebook.github.io/react-native/) 132 | - [Mux](https://mux.com/) 133 | - [Channels](https://pusher.com/channels) 134 | 135 | ## Donation 136 | 137 | If this project helped you reduce time to develop, please consider buying me a cup of coffee :) 138 | 139 | Buy Me A Coffee 140 | -------------------------------------------------------------------------------- /Root.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { YellowBox } from "react-native"; 3 | import { createAppContainer, createStackNavigator } from "react-navigation"; 4 | import Index from "./src/screens/Index"; 5 | import ViewStream from "./src/screens/ViewStream"; 6 | import PublishStream from "./src/screens/PublishStream"; 7 | 8 | YellowBox.ignoreWarnings(["Setting a timer", "Remote debugger"]); 9 | 10 | const RootStack = createStackNavigator( 11 | { 12 | Index, 13 | ViewStream, 14 | PublishStream 15 | }, 16 | { 17 | initialRouteName: "Index" 18 | } 19 | ); 20 | 21 | const AppContainer = createAppContainer(RootStack); 22 | 23 | class Router extends Component { 24 | render() { 25 | return ; 26 | } 27 | } 28 | 29 | export default Router; -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" 3 | 4 | import com.android.build.OutputFile 5 | 6 | /** 7 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 8 | * and bundleReleaseJsAndAssets). 9 | * These basically call `react-native bundle` with the correct arguments during the Android build 10 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 11 | * bundle directly from the development server. Below you can see all the possible configurations 12 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 13 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 14 | * 15 | * project.ext.react = [ 16 | * // the name of the generated asset file containing your JS bundle 17 | * bundleAssetName: "index.android.bundle", 18 | * 19 | * // the entry file for bundle generation 20 | * entryFile: "index.android.js", 21 | * 22 | * // whether to bundle JS and assets in debug mode 23 | * bundleInDebug: false, 24 | * 25 | * // whether to bundle JS and assets in release mode 26 | * bundleInRelease: true, 27 | * 28 | * // whether to bundle JS and assets in another build variant (if configured). 29 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 30 | * // The configuration property can be in the following formats 31 | * // 'bundleIn${productFlavor}${buildType}' 32 | * // 'bundleIn${buildType}' 33 | * // bundleInFreeDebug: true, 34 | * // bundleInPaidRelease: true, 35 | * // bundleInBeta: true, 36 | * 37 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 38 | * // for example: to disable dev mode in the staging build type (if configured) 39 | * devDisabledInStaging: true, 40 | * // The configuration property can be in the following formats 41 | * // 'devDisabledIn${productFlavor}${buildType}' 42 | * // 'devDisabledIn${buildType}' 43 | * 44 | * // the root of your project, i.e. where "package.json" lives 45 | * root: "../../", 46 | * 47 | * // where to put the JS bundle asset in debug mode 48 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 49 | * 50 | * // where to put the JS bundle asset in release mode 51 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 52 | * 53 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 54 | * // require('./image.png')), in debug mode 55 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 56 | * 57 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 58 | * // require('./image.png')), in release mode 59 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 60 | * 61 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 62 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 63 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 64 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 65 | * // for example, you might want to remove it from here. 66 | * inputExcludes: ["android/**", "ios/**"], 67 | * 68 | * // override which node gets called and with what additional arguments 69 | * nodeExecutableAndArgs: ["node"], 70 | * 71 | * // supply additional arguments to the packager 72 | * extraPackagerArgs: [] 73 | * ] 74 | */ 75 | 76 | project.ext.react = [ 77 | entryFile: "index.js" 78 | ] 79 | 80 | apply from: "../../node_modules/react-native/react.gradle" 81 | 82 | /** 83 | * Set this to true to create two separate APKs instead of one: 84 | * - An APK that only works on ARM devices 85 | * - An APK that only works on x86 devices 86 | * The advantage is the size of the APK is reduced by about 4MB. 87 | * Upload all the APKs to the Play Store and people will download 88 | * the correct one based on the CPU architecture of their device. 89 | */ 90 | def enableSeparateBuildPerCPUArchitecture = false 91 | 92 | /** 93 | * Run Proguard to shrink the Java bytecode in release builds. 94 | */ 95 | def enableProguardInReleaseBuilds = false 96 | 97 | android { 98 | compileSdkVersion rootProject.ext.compileSdkVersion 99 | buildToolsVersion rootProject.ext.buildToolsVersion 100 | 101 | defaultConfig { 102 | applicationId "com.rnstreamer" 103 | minSdkVersion rootProject.ext.minSdkVersion 104 | targetSdkVersion rootProject.ext.targetSdkVersion 105 | versionCode 1 106 | versionName "1.0" 107 | ndk { 108 | abiFilters "armeabi-v7a", "x86" 109 | } 110 | } 111 | splits { 112 | abi { 113 | reset() 114 | enable enableSeparateBuildPerCPUArchitecture 115 | universalApk false // If true, also generate a universal APK 116 | include "armeabi-v7a", "x86" 117 | } 118 | } 119 | buildTypes { 120 | release { 121 | minifyEnabled enableProguardInReleaseBuilds 122 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 123 | } 124 | } 125 | // applicationVariants are e.g. debug, release 126 | applicationVariants.all { variant -> 127 | variant.outputs.each { output -> 128 | // For each separate APK per architecture, set a unique version code as described here: 129 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 130 | def versionCodes = ["armeabi-v7a":1, "x86":2] 131 | def abi = output.getFilter(OutputFile.ABI) 132 | if (abi != null) { // null for the universal-debug, universal-release variants 133 | output.versionCodeOverride = 134 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 135 | } 136 | } 137 | } 138 | } 139 | 140 | dependencies { 141 | compile project(':@react-native-community_netinfo') 142 | compile project(':react-native-gesture-handler') 143 | compile project(':react-native-video') 144 | compile project(':react-native-nodemediaclient') 145 | compile project(':react-native-config') 146 | compile project(':@react-native-community_async-storage') 147 | implementation fileTree(dir: "libs", include: ["*.jar"]) 148 | implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" 149 | implementation "com.facebook.react:react-native:+" // From node_modules 150 | } 151 | 152 | // Run this once to be able to run the application with BUCK 153 | // puts all compile dependencies into folder libs for BUCK to use 154 | task copyDownloadableDepsToLibs(type: Copy) { 155 | from configurations.compile 156 | into 'libs' 157 | } 158 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/rnstreamer/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.rnstreamer; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "RNStreamer"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/rnstreamer/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.rnstreamer; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactApplication; 6 | import com.reactnativecommunity.netinfo.NetInfoPackage; 7 | import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; 8 | import com.brentvatne.react.ReactVideoPackage; 9 | import cn.nodemedia.react_native_nodemediaclient.NodeMediaReactPackage; 10 | import com.lugg.ReactNativeConfig.ReactNativeConfigPackage; 11 | import com.reactnativecommunity.asyncstorage.AsyncStoragePackage; 12 | import com.facebook.react.ReactNativeHost; 13 | import com.facebook.react.ReactPackage; 14 | import com.facebook.react.shell.MainReactPackage; 15 | import com.facebook.soloader.SoLoader; 16 | 17 | import java.util.Arrays; 18 | import java.util.List; 19 | 20 | public class MainApplication extends Application implements ReactApplication { 21 | 22 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 23 | @Override 24 | public boolean getUseDeveloperSupport() { 25 | return BuildConfig.DEBUG; 26 | } 27 | 28 | @Override 29 | protected List getPackages() { 30 | return Arrays.asList( 31 | new MainReactPackage(), 32 | new NetInfoPackage(), 33 | new RNGestureHandlerPackage(), 34 | new ReactVideoPackage(), 35 | new NodeMediaReactPackage(), 36 | new ReactNativeConfigPackage(), 37 | new AsyncStoragePackage() 38 | ); 39 | } 40 | 41 | @Override 42 | protected String getJSMainModuleName() { 43 | return "index"; 44 | } 45 | }; 46 | 47 | @Override 48 | public ReactNativeHost getReactNativeHost() { 49 | return mReactNativeHost; 50 | } 51 | 52 | @Override 53 | public void onCreate() { 54 | super.onCreate(); 55 | SoLoader.init(this, /* native exopackage */ false); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "27.0.3" 6 | minSdkVersion = 16 7 | compileSdkVersion = 27 8 | targetSdkVersion = 26 9 | supportLibVersion = "27.1.1" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath 'com.android.tools.build:gradle:3.1.4' 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | mavenLocal() 26 | google() 27 | jcenter() 28 | maven { 29 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 30 | url "$rootDir/../node_modules/react-native/android" 31 | } 32 | // Add this section 33 | maven { 34 | url 'https://jitpack.io' 35 | } 36 | } 37 | } 38 | 39 | 40 | task wrapper(type: Wrapper) { 41 | gradleVersion = '4.4' 42 | distributionUrl = distributionUrl.replace("bin", "all") 43 | } 44 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'RNStreamer' 2 | include ':@react-native-community_netinfo' 3 | project(':@react-native-community_netinfo').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/netinfo/android') 4 | include ':react-native-gesture-handler' 5 | project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android') 6 | include ':react-native-video' 7 | project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android') 8 | include ':react-native-nodemediaclient' 9 | project(':react-native-nodemediaclient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-nodemediaclient/android') 10 | include ':react-native-config' 11 | project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android') 12 | include ':@react-native-community_async-storage' 13 | project(':@react-native-community_async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android') 14 | 15 | include ':app' 16 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RNStreamer", 3 | "displayName": "RNStreamer" 4 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import {AppRegistry} from 'react-native'; 4 | import App from './App'; 5 | import {name as appName} from './app.json'; 6 | 7 | AppRegistry.registerComponent(appName, () => App); 8 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | target 'RNStreamer' do 2 | 3 | pod 'NodeMediaClient' 4 | 5 | end -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RNStreamer", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "test": "jest" 8 | }, 9 | "dependencies": { 10 | "@react-native-community/async-storage": "^1.6.1", 11 | "@react-native-community/netinfo": "3.2.1", 12 | "axios": "^0.19.0", 13 | "pusher-js": "^5.0.1", 14 | "random-animal-name-generator": "^0.1.1", 15 | "random-id": "^1.0.3", 16 | "react": "16.6.3", 17 | "react-native": "0.57.8", 18 | "react-native-config": "^0.11.7", 19 | "react-native-dialog": "^5.6.0", 20 | "react-native-gesture-handler": "1.2.1", 21 | "react-native-nodemediaclient": "^0.1.2", 22 | "react-native-permissions": "^1.2.0", 23 | "react-native-video": "^5.0.2", 24 | "react-navigation": "3.12.1" 25 | }, 26 | "devDependencies": { 27 | "babel-jest": "24.9.0", 28 | "jest": "24.9.0", 29 | "metro-react-native-babel-preset": "0.56.0", 30 | "react-test-renderer": "16.6.3" 31 | }, 32 | "jest": { 33 | "preset": "react-native" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/.env: -------------------------------------------------------------------------------- 1 | CHANNELS_APP_ID="YOUR PUSHER CHANNELS APP INSTANCE ID" 2 | CHANNELS_APP_KEY="YOUR PUSHER CHANNELS APP INSTANCE KEY" 3 | CHANNELS_APP_SECRET="YOUR PUSHER CHANNELS APP INSTANCE SECRET" 4 | CHANNELS_APP_CLUSTER="YOUR PUSHER CHANNELS APP INSTANCE CLUSTER" -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const bodyParser = require("body-parser"); 3 | const cors = require("cors"); 4 | const Pusher = require("pusher"); 5 | const randomId = require("random-id"); 6 | 7 | require("dotenv").config(); 8 | const app = express(); 9 | 10 | const pusher = new Pusher({ 11 | appId: process.env.CHANNELS_APP_ID, 12 | key: process.env.CHANNELS_APP_KEY, 13 | secret: process.env.CHANNELS_APP_SECRET, 14 | cluster: process.env.CHANNELS_APP_CLUSTER, 15 | }); 16 | 17 | app.use(bodyParser.urlencoded({ extended: false })); 18 | app.use(bodyParser.json()); 19 | app.use(cors()); 20 | 21 | const streams = []; 22 | 23 | app.post('/pusher/auth', (req, res) => { 24 | const socketId = req.body.socket_id; 25 | const channel = req.body.channel_name; 26 | const auth = pusher.authenticate(socketId, channel); 27 | res.send(auth); 28 | }); 29 | 30 | app.post("/stream", (req, res) => { 31 | const { id, mux_stream_key, mux_playback_id } = req.body; 32 | const index = streams.findIndex(strm => strm.id == id); 33 | if (index === -1) { 34 | streams.push({ 35 | id, 36 | mux_stream_key, 37 | mux_playback_id 38 | }); 39 | } 40 | res.send('ok'); 41 | }); 42 | 43 | app.get("/stream/:id", (req, res) => { 44 | const id = req.params.id; 45 | const data = streams.find(strm => strm.id == id); 46 | res.send({ playback_id: data.mux_playback_id }); 47 | }); 48 | 49 | app.get('/send/:channel_name/:comment', (req, res) => { 50 | pusher.trigger( 51 | `private-stream-${req.params.channel_name}`, 52 | 'client-viewer-comment', 53 | { id: randomId(5), text: req.params.comment } 54 | ); 55 | res.send('ok'); 56 | }); 57 | 58 | const PORT = 5000; 59 | app.listen(PORT, (err) => { 60 | if (err) { 61 | console.error(err); 62 | } else { 63 | console.log(`Running on ports ${PORT}`); 64 | } 65 | }); -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rn-streamer-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node index.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "axios": "^0.19.0", 14 | "body-parser": "^1.18.3", 15 | "cors": "^2.8.5", 16 | "dotenv": "^8.1.0", 17 | "express": "4.17.1", 18 | "pusher": "^2.2.2", 19 | "random-id": "^1.0.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/CommentList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, FlatList, Text, StyleSheet } from 'react-native'; 3 | 4 | export default class CommentList extends Component { 5 | 6 | render() { 7 | return ( 8 | 9 | item.id.toString()} 13 | ref="flatList" 14 | onContentSizeChange={()=> this.refs.flatList.scrollToEnd()} 15 | /> 16 | 17 | ); 18 | } 19 | 20 | 21 | _renderComment({ item }) { 22 | return ( 23 | 24 | {item.text} 25 | 26 | ); 27 | } 28 | 29 | 30 | } 31 | 32 | const styles = StyleSheet.create({ 33 | list: { 34 | height: 300, 35 | position: 'absolute', 36 | bottom: 30, 37 | left: 20 38 | }, 39 | comment: { 40 | padding: 10 41 | }, 42 | commentText: { 43 | color: '#FFF', 44 | fontSize: 14 45 | }, 46 | }); -------------------------------------------------------------------------------- /src/screens/Index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, Text, Button, Clipboard, StyleSheet } from "react-native"; 3 | import Dialog from "react-native-dialog"; 4 | import axios from "axios"; 5 | import Config from "react-native-config"; 6 | import Pusher from "pusher-js/react-native"; 7 | 8 | const generateRandomAnimalName = require('random-animal-name-generator'); 9 | 10 | const SERVER_BASE_URL = 'YOUR NGROK HTTPS URL'; 11 | 12 | const mux_instance = axios.create({ 13 | baseURL: 'https://api.mux.com', 14 | method: 'post', 15 | headers: { 'Content-Type': 'application/json' }, 16 | auth: { 17 | username: Config.MUX_TOKEN_ID, 18 | password: Config.MUX_TOKEN_SECRET 19 | } 20 | }); 21 | 22 | export default class Index extends Component { 23 | static navigationOptions = { 24 | header: null 25 | }; 26 | 27 | 28 | state = { 29 | showStreamIDInputDialog: false, 30 | showStreamIDDialog: false, 31 | streamIDToView: "", 32 | generatedStreamID: generateRandomAnimalName().replace(' ', '-').toLocaleLowerCase() + '-' + Math.floor(Math.random() * 100) 33 | } 34 | 35 | 36 | componentDidMount() { 37 | this.pusher = new Pusher(Config.CHANNELS_KEY, { 38 | authEndpoint: `${SERVER_BASE_URL}/pusher/auth`, 39 | cluster: Config.CHANNELS_CLUSTER, 40 | encrypted: true 41 | }); 42 | } 43 | 44 | 45 | render() { 46 | const { showStreamIDInputDialog, showStreamIDDialog, generatedStreamID } = this.state; 47 | return ( 48 | 49 | What do you want to do? 50 | 51 | 52 |