Repository: ezranbayantemur/react-native-rtmp-publisher
Branch: master
Commit: 59542fab5f54
Files: 132
Total size: 1010.5 KB
Directory structure:
gitextract_iogv7721/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ └── feature_request.md
│ └── workflows/
│ └── build.yml
├── .gitignore
├── .husky/
│ ├── .npmignore
│ ├── commit-msg
│ └── pre-commit
├── .yarnrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── __mocks__/
│ └── RTMPPublisher.js
├── android/
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── com/
│ └── reactnativertmppublisher/
│ ├── RTMPManager.java
│ ├── RTMPModule.java
│ ├── RTMPPackage.java
│ ├── enums/
│ │ ├── AudioInputType.java
│ │ ├── BluetoothDeviceStatuses.java
│ │ └── StreamState.java
│ ├── interfaces/
│ │ └── ConnectionListener.java
│ ├── modules/
│ │ ├── BluetoothDeviceConnector.java
│ │ ├── ConnectionChecker.java
│ │ ├── Publisher.java
│ │ └── SurfaceHolderHelper.java
│ └── utils/
│ └── ObjectCaster.java
├── babel.config.js
├── coverage/
│ ├── clover.xml
│ ├── coverage-final.json
│ ├── lcov-report/
│ │ ├── Component.tsx.html
│ │ ├── RTMPPublisher.tsx.html
│ │ ├── base.css
│ │ ├── block-navigation.js
│ │ ├── index.html
│ │ ├── prettify.css
│ │ ├── prettify.js
│ │ └── sorter.js
│ └── lcov.info
├── example/
│ ├── README.md
│ ├── android/
│ │ ├── app/
│ │ │ ├── build.gradle
│ │ │ ├── debug.keystore
│ │ │ ├── proguard-rules.pro
│ │ │ └── src/
│ │ │ ├── debug/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ └── java/
│ │ │ │ └── com/
│ │ │ │ └── example/
│ │ │ │ └── reactnativertmp/
│ │ │ │ └── ReactNativeFlipper.java
│ │ │ └── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java/
│ │ │ │ └── com/
│ │ │ │ └── example/
│ │ │ │ └── reactnativertmp/
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── MainApplication.java
│ │ │ │ └── newarchitecture/
│ │ │ │ ├── MainApplicationReactNativeHost.java
│ │ │ │ ├── components/
│ │ │ │ │ └── MainComponentsRegistry.java
│ │ │ │ └── modules/
│ │ │ │ └── MainApplicationTurboModuleManagerDelegate.java
│ │ │ ├── jni/
│ │ │ │ ├── CMakeLists.txt
│ │ │ │ ├── MainApplicationModuleProvider.cpp
│ │ │ │ ├── MainApplicationModuleProvider.h
│ │ │ │ ├── MainApplicationTurboModuleManagerDelegate.cpp
│ │ │ │ ├── MainApplicationTurboModuleManagerDelegate.h
│ │ │ │ ├── MainComponentsRegistry.cpp
│ │ │ │ ├── MainComponentsRegistry.h
│ │ │ │ └── OnLoad.cpp
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ └── rn_edit_text_material.xml
│ │ │ └── values/
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ ├── build.gradle
│ │ ├── gradle/
│ │ │ └── wrapper/
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ │ ├── gradle.properties
│ │ ├── gradlew
│ │ ├── gradlew.bat
│ │ └── settings.gradle
│ ├── app.json
│ ├── babel.config.js
│ ├── index.tsx
│ ├── ios/
│ │ ├── .xcode.env
│ │ ├── File.swift
│ │ ├── Podfile
│ │ ├── RtmpExample/
│ │ │ ├── AppDelegate.h
│ │ │ ├── AppDelegate.mm
│ │ │ ├── Images.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Info.plist
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── main.m
│ │ ├── RtmpExample-Bridging-Header.h
│ │ ├── RtmpExample.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ └── xcshareddata/
│ │ │ └── xcschemes/
│ │ │ └── RtmpExample.xcscheme
│ │ ├── RtmpExample.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ ├── assets/
│ │ │ └── app.json
│ │ └── main.jsbundle
│ ├── metro.config.js
│ ├── package.json
│ └── src/
│ ├── App.styles.tsx
│ ├── App.tsx
│ ├── components/
│ │ ├── Button/
│ │ │ ├── Button.styles.tsx
│ │ │ ├── Button.tsx
│ │ │ └── index.ts
│ │ ├── LiveBadge/
│ │ │ ├── LiveBadge.styles.tsx
│ │ │ ├── LiveBadge.tsx
│ │ │ └── index.ts
│ │ └── MicrophoneSelectModal/
│ │ ├── MicrophoneSelectModal.styles.tsx
│ │ ├── MicrophoneSelectModal.tsx
│ │ └── index.ts
│ └── hooks/
│ └── usePermissions.ts
├── ios/
│ ├── Podfile
│ ├── RTMPCreator.swift
│ ├── RTMPManager/
│ │ ├── RTMPView.swift
│ │ ├── RTMPViewManager.m
│ │ └── RTMPViewManager.swift
│ ├── RTMPModule/
│ │ ├── RTMPModule.m
│ │ └── RTMPModule.swift
│ ├── RtmpPublish-Bridging-Header.h
│ ├── RtmpPublish.xcodeproj/
│ │ └── project.pbxproj
│ └── RtmpPublish.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcshareddata/
│ └── IDEWorkspaceChecks.plist
├── jest.config.js
├── package.json
├── react-native-rtmp-publisher.podspec
├── scripts/
│ └── bootstrap.js
├── src/
│ ├── Component.tsx
│ ├── RTMPPublisher.tsx
│ ├── index.tsx
│ ├── test/
│ │ ├── RTMPPublisher.test.tsx
│ │ └── __snapshots__/
│ │ └── RTMPPublisher.test.tsx.snap
│ └── types.ts
├── tsconfig.build.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
================================================
FILE: .gitattributes
================================================
*.pbxproj -text
# specific for windows script files
*.bat text eol=crlf
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: File a bug report
body:
- type: textarea
id: describe-the-bug
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: to-reproduce
attributes:
label: To Reproduce
description: Steps to reproduce the behavior
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: Steps to reproduce the behavior
validations:
required: true
- type: textarea
id: version
attributes:
label: Version
description: What version of our software are you running?
validations:
required: true
- type: textarea
id: smartphone-info
attributes:
label: Smartphone info.
description: please complete the following information
placeholder: |
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- type: textarea
id: addditional-context
attributes:
label: Additional context
description: Add any other context about the problem here.
- type: textarea
id: screenshot
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem.
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on:
pull_request:
branches:
- master
- dev
jobs:
build_dependency:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Caching node_modules
id: cache-npm
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ./node_modules
key: rtmp-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
- name: Caching Pods
id: cache-pods
uses: actions/cache@v3
env:
cache-name: cache-cocoapods
with:
path: ./example/ios/Pods
key: rtmp-build-${{ env.cache-name }}-${{ hashFiles('./example/ios/Podfile.lock') }}
- name: Install dependencies
run: |
yarn
- name: Lint files
run: |
yarn lint
- name: Typescript checking
run: |
yarn typescript
- name: Unit tests
run: |
yarn test --coverage --updateSnapshot --verbose
- name: Building the package
run: |
yarn prepare
build_android:
needs: build_dependency
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Caching node_modules
id: cache-npm
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ./node_modules
key: rtmp-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
- name: Install dependencies
run: |
yarn
- name: Caching Gradle
id: cache-gradle
uses: actions/cache@v3
env:
cache-name: cache-gradle-files
with:
path: ./example/android/.gradle
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./example/android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
- name: Install dependencies
run: |
yarn
- name: Building Android
run: |
cd example/android && ./gradlew build
build_ios:
needs: build_dependency
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Caching node_modules
id: cache-npm
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ./node_modules
key: rtmp-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
- name: Caching Pods
id: cache-pods
uses: actions/cache@v3
env:
cache-name: cache-cocoapods
with:
path: ./example/ios/Pods
key: rtmp-build-${{ env.cache-name }}-${{ hashFiles('./example/ios/Podfile.lock') }}
- name: Install dependencies
run: |
yarn
- name: Building iOS
run: |
cd example/ios && xcodebuild -quiet -workspace RtmpExample.xcworkspace -scheme RtmpExample -destination generic/platform=iOS CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO build
================================================
FILE: .gitignore
================================================
# OSX
#
.DS_Store
# XDE
.expo/
# VSCode
.vscode/
jsconfig.json
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IJ
#
.classpath
.cxx
.gradle
.idea
.project
.settings
local.properties
android.iml
# Cocoapods
#
example/ios/Pods
/ios/Pods
# node.js
#
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
android/app/libs
android/keystores/debug.keystore
# Expo
.expo/*
# generated by bob
lib/
example/.env
.env
================================================
FILE: .husky/.npmignore
================================================
_
================================================
FILE: .husky/commit-msg
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn commitlint -E HUSKY_GIT_PARAMS
================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint && yarn typescript
================================================
FILE: .yarnrc
================================================
# Override Yarn command so we can automatically setup the repo on running `yarn`
yarn-path "scripts/bootstrap.js"
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project.
## Development workflow
To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
```sh
yarn
```
> While it's possible to use [`npm`](https://github.com/npm/cli), the tooling is built around [`yarn`](https://classic.yarnpkg.com/), so you'll have an easier time if you use `yarn` for development.
While developing, you can run the [example app](/example/) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app.
To start the packager:
```sh
yarn example start
```
To run the example app on Android:
```sh
yarn example android
```
To run the example app on iOS:
```sh
yarn example ios
```
Make sure your code passes TypeScript and ESLint. Run the following to verify:
```sh
yarn typescript
yarn lint
```
To fix formatting errors, run the following:
```sh
yarn lint --fix
```
Remember to add tests for your change if possible. Run the unit tests by:
```sh
yarn test
```
To edit the Objective-C files, open `example/ios/RtmpPublisherExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-rtmp-publisher`.
To edit the Kotlin files, open `example/android` in Android studio and find the source files at `reactnativertmppublisher` under `Android`.
### Commit message convention
We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
- `fix`: bug fixes, e.g. fix crash due to deprecated method.
- `feat`: new features, e.g. add new method to the module.
- `refactor`: code refactor, e.g. migrate from class components to hooks.
- `docs`: changes into documentation, e.g. add usage example for the module..
- `test`: adding or updating tests, e.g. add integration tests using detox.
- `chore`: tooling changes, e.g. change CI config.
Our pre-commit hooks verify that your commit message matches this format when committing.
### Linting and tests
[ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
Our pre-commit hooks verify that the linter and tests pass when committing.
### Publishing to npm
We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc.
To publish new versions, run the following:
```sh
yarn release
```
### Scripts
The `package.json` file contains various scripts for common tasks:
- `yarn bootstrap`: setup project by installing all dependencies and pods.
- `yarn typescript`: type-check files with TypeScript.
- `yarn lint`: lint files with ESLint.
- `yarn test`: run unit tests with Jest.
- `yarn example start`: start the Metro server for the example app.
- `yarn example android`: run the example app on Android.
- `yarn example ios`: run the example app on iOS.
### Sending a pull request
> **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
When you're sending a pull request:
- Prefer small pull requests focused on one change.
- Verify that linters and tests are passing.
- Review the documentation to make sure it looks good.
- Follow the pull request template when opening a pull request.
- For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
## Code of Conduct
### Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
### Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
### Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
### Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
### Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
### Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
#### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
#### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
#### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
#### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Ezran
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
### ⚠️⚠️⚠️ THIS PACKAGE CURRENTLY UNMAINTENED. WILL RE-MAINTENED IN SOON WITH NEW ARCH ⚠️⚠️⚠️
📹 Live stream RTMP publisher for React Native with built in camera support!
## Installation
```sh
npm install react-native-rtmp-publisher
```
or
```sh
yarn add react-native-rtmp-publisher
```
and for iOS
```sh
cd ios && pod install
```
## Android
Add Android Permission for camera and audio to `AndroidManifest.xml`
```xml
```
## iOS
Add iOS Permission for camera and audio to `Info.plist`
```xml
NSCameraUsageDescriptionCAMERA PERMISSION DESCRIPTIONNSMicrophoneUsageDescriptionAUDIO PERMISSION DESCRIPTION
```
Implement these changes to `AppDelegate.m` (or `AppDelegate.mm`)
```objc
#import // <-- Add this import
..
..
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
..
..
..
..
// <-- Add this section -->
AVAudioSession *session = AVAudioSession.sharedInstance;
NSError *error = nil;
if (@available(iOS 10.0, *)) {
[session
setCategory:AVAudioSessionCategoryPlayAndRecord
mode:AVAudioSessionModeVoiceChat
options:AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionAllowBluetooth
error:&error];
} else {
SEL selector = NSSelectorFromString(@"setCategory:withOptions:error:");
NSArray * optionsArray =
[NSArray arrayWithObjects:
[NSNumber numberWithInteger:AVAudioSessionCategoryOptionAllowBluetooth],
[NSNumber numberWithInteger:AVAudioSessionCategoryOptionDefaultToSpeaker], nil];
[session
performSelector:selector
withObject: AVAudioSessionCategoryPlayAndRecord
withObject: optionsArray
];
[session
setMode:AVAudioSessionModeVoiceChat
error:&error
];
}
[session
setActive:YES
error:&error
];
// <-- Add this section -->
return YES;
}
```
## Example Project
Clone the repo and run
```sh
yarn
```
and
```sh
cd example && yarn ios (or yarn android)
```
You can use Youtube for live stream server. You can check [Live on Youtube](https://support.google.com/youtube/answer/2474026?hl=tr&ref_topic=9257984)
## Usage
```js
import RTMPPublisher from 'react-native-rtmp-publisher';
// ...
async function publisherActions() {
await publisherRef.current.startStream();
await publisherRef.current.stopStream();
await publisherRef.current.mute();
await publisherRef.current.unmute();
await publisherRef.current.switchCamera();
await publisherRef.current.getPublishURL();
await publisherRef.current.isMuted();
await publisherRef.current.isStreaming();
await publisherRef.current.toggleFlash();
await publisherRef.current.hasCongestion();
await publisherRef.current.isAudioPrepared();
await publisherRef.current.isVideoPrepared();
await publisherRef.current.isCameraOnPreview();
await publisherRef.current.setAudioInput(audioInput: AudioInputType);
}
...}
onConnectionStartedRtmp={() => ...}
onConnectionSuccessRtmp={() => ...}
onDisconnectRtmp={() => ...}
onNewBitrateRtmp={() => ...}
onStreamStateChanged={(status: streamState) => ...}
/>
```
## Props
| Name | Type | Required | Description |
| :----------: | :------: | :------: | :-----------------------------------: |
| `streamURL` | `string` | `true` | Publish URL address with RTM Protocol |
| `streamName` | `string` | `true` | Stream name or key |
### Youtube Example
For live stream, Youtube gives you stream url and stream key, you can place the key on `streamName` parameter
**Youtube Stream URL:** `rtmp://a.rtmp.youtube.com/live2`
**Youtube Stream Key:** `****-****-****-****-****`
```js
` | Starts the stream | ✅ | ✅ |
| `stopStream` | `Promise` | Stops the stream | ✅ | ✅ |
| `mute` | `Promise` | Mutes the microphone | ✅ | ✅ |
| `unmute` | `Promise` | Unmutes the microphone | ✅ | ✅ |
| `switchCamera` | `Promise` | Switches the camera | ✅ | ✅ |
| `toggleFlash` | `Promise` | Toggles the flash | ✅ | ✅ |
| `getPublishURL` | `Promise` | Gets the publish URL | ✅ | ✅ |
| `isMuted` | `Promise` | Returns microphone state | ✅ | ✅ |
| `isStreaming` | `Promise` | Returns streaming state | ✅ | ✅ |
| `hasCongestion` | `Promise` | Returns if congestion | ✅ | ❌ |
| `isAudioPrepared` | `Promise` | Returns audio prepare state | ✅ | ✅ |
| `isVideoPrepared` | `Promise` | Returns video prepare state | ✅ | ✅ |
| `isCameraOnPreview` | `Promise` | Returns camera is on | ✅ | ❌ |
| `setAudioInput` | `Promise`| Sets microphone input | ✅ | ✅ |
## Types
| Name | Value |
| ------------------------- | :--------------------------------------------------:|
| `streamState` | `CONNECTING`, `CONNECTED`, `DISCONNECTED`, `FAILED` |
| `BluetoothDeviceStatuses` | `CONNECTING`, `CONNECTED`, `DISCONNECTED` |
| `AudioInputType` | `BLUETOOTH_HEADSET`, `SPEAKER`, `WIRED_HEADSET` |
* AudioInputType: WIRED_HEADSET type supporting in only iOS. On Android it affects nothing. If a wired headset connected to Android device, device uses it as default.
## Used Native Packages
- Android: [rtmp-rtsp-stream-client-java](https://github.com/pedroSG94/rtmp-rtsp-stream-client-java) [2.2.2]
- iOS: [HaishinKit.swift](https://github.com/shogo4405/HaishinKit.swift) [1.2.7]
## Contributing
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
## License
MIT
================================================
FILE: __mocks__/RTMPPublisher.js
================================================
import { NativeModules } from 'react-native';
NativeModules.RTMPPublisher = {
startStream: jest.fn(),
stopStream: jest.fn(),
isStreaming: jest.fn(),
isCameraOnPreview: jest.fn(),
getPublishURL: jest.fn(),
hasCongestion: jest.fn(),
isAudioPrepared: jest.fn(),
isVideoPrepared: jest.fn(),
isMuted: jest.fn(),
mute: jest.fn(),
unmute: jest.fn(),
switchCamera: jest.fn(),
toggleFlash: jest.fn(),
};
================================================
FILE: android/build.gradle
================================================
buildscript {
if (project == rootProject) {
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
}
}
}
apply plugin: 'com.android.library'
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
android {
compileSdkVersion safeExtGet('Rtmp_compileSdkVersion', 31)
defaultConfig {
minSdkVersion safeExtGet('Rtmp_minSdkVersion', 21)
targetSdkVersion safeExtGet('Rtmp_targetSdkVersion', 31)
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
lintOptions {
disable 'GradleCompatible'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
repositories {
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
google()
mavenCentral()
jcenter()
maven { url 'https://www.jitpack.io' }
}
dependencies {
//noinspection GradleDynamicVersion
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.github.pedroSG94.rtmp-rtsp-stream-client-java:rtplibrary:2.2.2'
implementation "com.facebook.react:react-native:+" // From node_modules
}
================================================
FILE: android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: android/gradlew
================================================
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
================================================
FILE: android/gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: android/src/main/AndroidManifest.xml
================================================
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/RTMPManager.java
================================================
package com.reactnativertmppublisher;
import android.view.SurfaceView;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.reactnativertmppublisher.modules.Publisher;
import com.reactnativertmppublisher.modules.SurfaceHolderHelper;
import java.util.Map;
public class RTMPManager extends SimpleViewManager {
//TODO: "Do not place Android context classes in static fields (static reference to Publisher which has field _surfaceView pointing to SurfaceView); this is a memory leak"
public static Publisher publisher;
public final String REACT_CLASS_NAME = "RTMPPublisher";
SurfaceView surfaceView;
private ThemedReactContext _reactContext;
View.OnLayoutChangeListener onLayoutChangeListener = new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(@NonNull View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
}
};
@NonNull
@Override
public String getName() {
return REACT_CLASS_NAME;
}
@NonNull
@Override
protected SurfaceView createViewInstance(@NonNull ThemedReactContext reactContext) {
_reactContext = reactContext;
surfaceView = new SurfaceView(_reactContext);
publisher = new Publisher(_reactContext, surfaceView);
surfaceView.addOnLayoutChangeListener(onLayoutChangeListener);
SurfaceHolderHelper surfaceHolderHelper = new SurfaceHolderHelper(_reactContext, publisher.getRtmpCamera(), surfaceView.getId());
surfaceView.getHolder().addCallback(surfaceHolderHelper);
return surfaceView;
}
@ReactProp(name = "streamURL")
public void setStreamURL(SurfaceView surfaceView, @Nullable String url) {
publisher.setStreamUrl(url);
}
@ReactProp(name = "streamName")
public void setStreamName(SurfaceView surfaceView, @Nullable String name) {
publisher.setStreamName(name);
}
@Nullable
@Override
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.builder()
.put("onDisconnect", MapBuilder.of("registrationName", "onDisconnect"))
.put("onConnectionFailed", MapBuilder.of("registrationName", "onConnectionFailed"))
.put("onConnectionStarted", MapBuilder.of("registrationName", "onConnectionStarted"))
.put("onConnectionSuccess", MapBuilder.of("registrationName", "onConnectionSuccess"))
.put("onNewBitrateReceived", MapBuilder.of("registrationName", "onNewBitrateReceived"))
.put("onStreamStateChanged", MapBuilder.of("registrationName", "onStreamStateChanged"))
.put("onBluetoothDeviceStatusChanged", MapBuilder.of("registrationName", "onBluetoothDeviceStatusChanged"))
.build();
}
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/RTMPModule.java
================================================
package com.reactnativertmppublisher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.reactnativertmppublisher.enums.AudioInputType;
public class RTMPModule extends ReactContextBaseJavaModule {
private final String REACT_MODULE_NAME = "RTMPPublisher";
public RTMPModule(@Nullable ReactApplicationContext reactContext) {
super(reactContext);
}
@NonNull
@Override
public String getName() {
return REACT_MODULE_NAME;
}
@ReactMethod
public void isStreaming(Promise promise) {
try {
boolean streamStatus = RTMPManager.publisher.isStreaming();
promise.resolve(streamStatus);
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void isCameraOnPreview(Promise promise) {
try {
boolean streamStatus = RTMPManager.publisher.isOnPreview();
promise.resolve(streamStatus);
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void getPublishURL(Promise promise) {
try {
String url = RTMPManager.publisher.getPublishURL();
promise.resolve(url);
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void hasCongestion(Promise promise) {
try {
boolean congestionStatus = RTMPManager.publisher.hasCongestion();
promise.resolve(congestionStatus);
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void isAudioPrepared(Promise promise) {
try {
boolean status = RTMPManager.publisher.isAudioPrepared();
promise.resolve(status);
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void isVideoPrepared(Promise promise) {
try {
boolean status = RTMPManager.publisher.isVideoPrepared();
promise.resolve(status);
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void isMuted(Promise promise) {
try {
boolean status = RTMPManager.publisher.isAudioMuted();
promise.resolve(status);
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void mute(Promise promise) {
try {
if (RTMPManager.publisher.isAudioMuted()) {
return;
}
RTMPManager.publisher.disableAudio();
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void unmute(Promise promise) {
try {
if (!RTMPManager.publisher.isAudioMuted()) {
return;
}
RTMPManager.publisher.enableAudio();
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void switchCamera(Promise promise) {
try {
RTMPManager.publisher.switchCamera();
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void startStream(Promise promise) {
try {
RTMPManager.publisher.startStream();
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void stopStream(Promise promise) {
try {
RTMPManager.publisher.stopStream();
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void toggleFlash(Promise promise) {
try {
RTMPManager.publisher.toggleFlash();
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void setAudioInput(int audioInputType, Promise promise) {
try {
AudioInputType selectedType = AudioInputType.values()[audioInputType];
RTMPManager.publisher.setAudioInput(selectedType);
} catch (Exception e) {
promise.reject(e);
}
}
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/RTMPPackage.java
================================================
package com.reactnativertmppublisher;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class RTMPPackage implements ReactPackage {
@NonNull
@Override
public List createNativeModules(@NonNull ReactApplicationContext reactContext) {
List modules = new ArrayList<>();
modules.add(new RTMPModule(reactContext));
return modules;
}
@NonNull
@Override
public List createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.singletonList(
new RTMPManager()
);
}
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/enums/AudioInputType.java
================================================
package com.reactnativertmppublisher.enums;
public enum AudioInputType {
BLUETOOTH_HEADSET,
SPEAKER
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/enums/BluetoothDeviceStatuses.java
================================================
package com.reactnativertmppublisher.enums;
public enum BluetoothDeviceStatuses {
CONNECTING,
CONNECTED,
DISCONNECTED
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/enums/StreamState.java
================================================
package com.reactnativertmppublisher.enums;
public enum StreamState {
CONNECTING,
CONNECTED,
DISCONNECTED,
FAILED
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/interfaces/ConnectionListener.java
================================================
package com.reactnativertmppublisher.interfaces;
import androidx.annotation.Nullable;
public interface ConnectionListener {
void onChange(String type, @Nullable Object data);
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/modules/BluetoothDeviceConnector.java
================================================
package com.reactnativertmppublisher.modules;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import com.reactnativertmppublisher.enums.BluetoothDeviceStatuses;
import com.reactnativertmppublisher.interfaces.ConnectionListener;
import java.util.ArrayList;
import java.util.List;
public class BluetoothDeviceConnector extends BroadcastReceiver implements BluetoothProfile.ServiceListener{
private final List listeners = new ArrayList<>();
public void addListener(ConnectionListener listener) {
listeners.add(listener);
}
public BluetoothDeviceConnector(Context context) {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.getProfileProxy(context, this, BluetoothProfile.HEADSET);
context.registerReceiver(this, new IntentFilter(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED));
}
@Override
public void onServiceConnected(int i, BluetoothProfile bluetoothProfile) {
if(bluetoothProfile.getConnectedDevices().size() > 0) {
for (ConnectionListener l : listeners) {
l.onChange("onBluetoothDeviceStatusChanged", BluetoothDeviceStatuses.CONNECTED.toString());
}
}
}
@Override
public void onServiceDisconnected(int i) {
for (ConnectionListener l : listeners) {
l.onChange("onBluetoothDeviceStatusChanged", BluetoothDeviceStatuses.DISCONNECTED.toString());
}
}
@Override
public void onReceive(Context context, Intent intent) {
int status = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1);
switch (status){
case BluetoothAdapter.STATE_CONNECTING: {
for (ConnectionListener l : listeners) {
l.onChange("onBluetoothDeviceStatusChanged", BluetoothDeviceStatuses.CONNECTING.toString());
};
break;
}
case BluetoothAdapter.STATE_CONNECTED: {
for (ConnectionListener l : listeners) {
l.onChange("onBluetoothDeviceStatusChanged", BluetoothDeviceStatuses.CONNECTED.toString());
};
break;
}
case BluetoothAdapter.STATE_DISCONNECTED: {
for (ConnectionListener l : listeners) {
l.onChange("onBluetoothDeviceStatusChanged", BluetoothDeviceStatuses.DISCONNECTED.toString());
};
break;
}
};
}
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/modules/ConnectionChecker.java
================================================
package com.reactnativertmppublisher.modules;
import androidx.annotation.NonNull;
import com.pedro.rtmp.utils.ConnectCheckerRtmp;
import com.reactnativertmppublisher.interfaces.ConnectionListener;
import java.util.ArrayList;
import java.util.List;
public class ConnectionChecker implements ConnectCheckerRtmp {
private final List listeners = new ArrayList<>();
public void addListener(ConnectionListener listener) {
listeners.add(listener);
}
@Override
public void onAuthErrorRtmp() {
for (ConnectionListener l : listeners) {
l.onChange("onAuthError", null);
}
}
@Override
public void onAuthSuccessRtmp() {
for (ConnectionListener l : listeners) {
l.onChange("onAuthSuccess", null);
}
}
// TODO: Parameters will be send after onChange method updated
@Override
public void onConnectionFailedRtmp(@NonNull String s) {
for (ConnectionListener l : listeners) {
l.onChange("onConnectionFailed", s);
}
}
// TODO: Parameters will be send after onChange method updated
@Override
public void onConnectionStartedRtmp(@NonNull String s) {
for (ConnectionListener l : listeners) {
l.onChange("onConnectionStarted", s);
}
}
@Override
public void onConnectionSuccessRtmp() {
for (ConnectionListener l : listeners) {
l.onChange("onConnectionSuccess", null);
}
}
@Override
public void onDisconnectRtmp() {
for (ConnectionListener l : listeners) {
l.onChange("onDisconnect", null);
}
}
// TODO: Parameters will be send after onChange method updated
@Override
public void onNewBitrateRtmp(long b) {
for (ConnectionListener l : listeners) {
l.onChange("onNewBitrateReceived", b);
}
}
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/modules/Publisher.java
================================================
package com.reactnativertmppublisher.modules;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.util.Log;
import android.view.SurfaceView;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.pedro.rtplibrary.rtmp.RtmpCamera1;
import com.reactnativertmppublisher.enums.AudioInputType;
import com.reactnativertmppublisher.enums.StreamState;
import com.reactnativertmppublisher.interfaces.ConnectionListener;
import com.reactnativertmppublisher.utils.ObjectCaster;
public class Publisher {
private final SurfaceView _surfaceView;
private final RtmpCamera1 _rtmpCamera;
private final ThemedReactContext _reactContext;
private final AudioManager _mAudioManager;
private String _streamUrl;
private String _streamName;
ConnectionChecker _connectionChecker = new ConnectionChecker();
BluetoothDeviceConnector _bluetoothDeviceConnector;
public Publisher(ThemedReactContext reactContext, SurfaceView surfaceView) {
_reactContext = reactContext;
_surfaceView = surfaceView;
_rtmpCamera = new RtmpCamera1(surfaceView, _connectionChecker);
_bluetoothDeviceConnector = new BluetoothDeviceConnector(reactContext);
_bluetoothDeviceConnector.addListener(createBluetoothDeviceListener());
_connectionChecker.addListener(createConnectionListener());
_mAudioManager = (AudioManager) reactContext.getSystemService(Context.AUDIO_SERVICE);
setAudioInput(AudioInputType.SPEAKER);
}
public RtmpCamera1 getRtmpCamera() {
return _rtmpCamera;
}
public ConnectionListener createConnectionListener() {
return (type, data) -> {
eventEffect(type);
WritableMap eventData = ObjectCaster.caster(data);
_reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(_surfaceView.getId(), type, eventData);
};
}
public ConnectionListener createBluetoothDeviceListener(){
return (type, data) -> {
eventEffect(type);
WritableMap eventData = ObjectCaster.caster(data);
_reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(_surfaceView.getId(), type, eventData);
};
}
private void eventEffect(@NonNull String eventType) {
switch (eventType) {
case "onConnectionStarted": {
WritableMap event = Arguments.createMap();
event.putString("data", String.valueOf(StreamState.CONNECTING));
_reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(_surfaceView.getId(), "onStreamStateChanged", event);
break;
}
case "onConnectionSuccess": {
WritableMap event = Arguments.createMap();
event.putString("data", String.valueOf(StreamState.CONNECTED));
_reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(_surfaceView.getId(), "onStreamStateChanged", event);
break;
}
case "onDisconnect": {
WritableMap event = Arguments.createMap();
event.putString("data", String.valueOf(StreamState.DISCONNECTED));
_reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(_surfaceView.getId(), "onStreamStateChanged", event);
break;
}
case "onConnectionFailed": {
WritableMap event = Arguments.createMap();
event.putString("data", String.valueOf(StreamState.FAILED));
_reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(_surfaceView.getId(), "onStreamStateChanged", event);
break;
}
}
}
//region COMPONENT METHODS
public String getPublishURL() {
return _streamUrl + "/" + _streamName;
}
public void setStreamUrl(String _streamUrl) {
this._streamUrl = _streamUrl;
}
public void setStreamName(String _streamName) {
this._streamName = _streamName;
}
public boolean isStreaming() {
return _rtmpCamera.isStreaming();
}
public boolean isOnPreview() {
return _rtmpCamera.isOnPreview();
}
public boolean isAudioPrepared() {
return _rtmpCamera.prepareAudio();
}
public boolean isVideoPrepared() {
return _rtmpCamera.prepareVideo();
}
public boolean hasCongestion() {
return _rtmpCamera.hasCongestion();
}
public boolean isAudioMuted() {
return _rtmpCamera.isAudioMuted();
}
public void disableAudio() {
_rtmpCamera.disableAudio();
}
public void enableAudio() {
_rtmpCamera.enableAudio();
}
public void switchCamera() {
_rtmpCamera.switchCamera();
}
public void toggleFlash() {
try {
if(_rtmpCamera.isLanternEnabled()){
_rtmpCamera.disableLantern();
return;
}
_rtmpCamera.enableLantern();
}
catch (Exception e){
e.printStackTrace();
}
}
public void startStream() {
try {
boolean isAudioPrepared = _rtmpCamera.prepareAudio(MediaRecorder.AudioSource.DEFAULT, 128 * 1024, 44100, true, false, false);
boolean isVideoPrepared = _rtmpCamera.prepareVideo(1280 , 720, 3000 * 1024);
if (!isAudioPrepared || !isVideoPrepared || _streamName == null || _streamUrl == null) {
return;
}
String url = _streamUrl + "/" + _streamName;
_rtmpCamera.startStream(url);
} catch (Exception e) {
e.printStackTrace();
}
}
public void stopStream() {
try {
boolean isStreaming = _rtmpCamera.isStreaming();
if (!isStreaming) {
return;
}
_rtmpCamera.stopStream();
} catch (Exception e) {
e.printStackTrace();
}
}
public void setAudioInput(@NonNull AudioInputType audioInputType){
System.out.println(audioInputType);
switch (audioInputType){
case BLUETOOTH_HEADSET: {
System.out.println("ble");
try{
_mAudioManager.startBluetoothSco();
_mAudioManager.setBluetoothScoOn(true);
break;
}
catch (Exception error){
System.out.println(error);
break;
}
}
case SPEAKER:{
try{
if(_mAudioManager.isBluetoothScoOn()){
_mAudioManager.stopBluetoothSco();
_mAudioManager.setBluetoothScoOn(false);
}
_mAudioManager.setSpeakerphoneOn(true);
break;
}
catch (Exception error){
System.out.println(error);
break;
}
}
}
}
//endregion
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/modules/SurfaceHolderHelper.java
================================================
package com.reactnativertmppublisher.modules;
import android.view.SurfaceHolder;
import androidx.annotation.NonNull;
import com.facebook.react.uimanager.ThemedReactContext;
import com.pedro.rtplibrary.rtmp.RtmpCamera1;
public class SurfaceHolderHelper implements SurfaceHolder.Callback {
private final RtmpCamera1 _rtmpCamera1;
public SurfaceHolderHelper(ThemedReactContext reactContext, RtmpCamera1 rtmpCamera1, int surfaceId) {
_rtmpCamera1 = rtmpCamera1;
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
_rtmpCamera1.startPreview(1280 , 720);
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
_rtmpCamera1.stopPreview();
}
}
================================================
FILE: android/src/main/java/com/reactnativertmppublisher/utils/ObjectCaster.java
================================================
package com.reactnativertmppublisher.utils;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
public class ObjectCaster {
public static WritableMap caster(@Nullable Object data){
WritableMap event = Arguments.createMap();
if(data == null){
event.putNull("data");
}
if(data instanceof String){
event.putString("data", (String) data);
}
if(data instanceof Integer){
event.putInt("data", (Integer) data);
}
if(data instanceof Boolean){
event.putBoolean("data", (Boolean) data);
}
if(data instanceof Long){
event.putDouble("data", (Long) data);
}
return event;
}
}
================================================
FILE: babel.config.js
================================================
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};
================================================
FILE: coverage/clover.xml
================================================
================================================
FILE: coverage/coverage-final.json
================================================
{}
================================================
FILE: coverage/lcov-report/Component.tsx.html
================================================
Code coverage report for Component.tsx