Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: react-native-webrtc/react-native-webrtc
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: ToffeeShare/react-native-webrtc
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.
  • 5 commits
  • 6 files changed
  • 2 contributors

Commits on Jan 26, 2022

  1. Copy the full SHA
    bb93e73 View commit details

Commits on Nov 1, 2022

  1. merged both branches

    Stofkat committed Nov 1, 2022
    Copy the full SHA
    db70f20 View commit details

Commits on Nov 9, 2022

  1. Copy the full SHA
    9fa6ebd View commit details
  2. Update README.md

    Stofkat authored Nov 9, 2022
    Copy the full SHA
    1f75d5f View commit details
  3. Update README.md

    Stofkat authored Nov 9, 2022
    Copy the full SHA
    9faa2bd View commit details
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
[<img src="https://avatars.githubusercontent.com/u/42463376" alt="React Native WebRTC" style="height: 6em;" />](https://github.com/react-native-webrtc/react-native-webrtc)

## ! Please note: changes for ToffeeShare
The data channel is now used to directly access the file system in order to quickly send data natively without first having to convert it to base64 using the native bridge.
I consider this to be a bit of a dirty hack, but it works for our use case. Feel free to use this version of WebRTC if this use case fits your project as well.

Furthermore it does have a proper implementation of the buffer outrun events that was lacking in the original repository. I should probably make a pull request of this..

# React-Native-WebRTC

[![npm version](https://img.shields.io/npm/v/react-native-webrtc)](https://www.npmjs.com/package/react-native-webrtc)
155 changes: 155 additions & 0 deletions RTCDataChannel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
'use strict';

import { NativeModules } from 'react-native';
import base64 from 'base64-js';
import EventTarget from 'event-target-shim';
import MessageEvent from './MessageEvent';
import RTCDataChannelEvent from './RTCDataChannelEvent';
import EventEmitter from './EventEmitter';

const {WebRTCModule} = NativeModules;

type RTCDataChannelState =
'connecting' |
'open' |
'closing' |
'closed';

const DATA_CHANNEL_EVENTS = [
'open',
'message',
'bufferedamountlow',
'closing',
'close',
'error',
];

export default class RTCDataChannel extends EventTarget(DATA_CHANNEL_EVENTS) {
_peerConnectionId: number;
_reactTag: string;

_id: number;
_label: string;
_maxPacketLifeTime: ?number;
_maxRetransmits: ?number;
_negotiated: boolean;
_ordered: boolean;
_protocol: string;
_readyState: RTCDataChannelState;

binaryType: 'arraybuffer' = 'arraybuffer'; // we only support 'arraybuffer'
bufferedAmount: number = 0;
bufferedAmountLowThreshold: number = 0;

onopen: ?Function;
onmessage: ?Function;
onbufferedamountlow: ?Function;
onerror: ?Function;
onclose: ?Function;

constructor(info) {
super();

this._peerConnectionId = info.peerConnectionId;
this._reactTag = info.reactTag;

this._label = info.label;
this._id = info.id === -1 ? null : info.id; // null until negotiated.
this._ordered = Boolean(info.ordered);
this._maxPacketLifeTime = info.maxPacketLifeTime;
this._maxRetransmits = info.maxRetransmits;
this._protocol = info.protocol || '';
this._negotiated = Boolean(info.negotiated);
this._readyState = info.readyState;

this._registerEvents();
}

get label(): string {
return this._label;
}

get id(): number {
return this._id;
}

get ordered(): boolean {
return this._ordered;
}

get maxPacketLifeTime(): number {
return this._maxPacketLifeTime;
}

get maxRetransmits(): number {
return this._maxRetransmits;
}

get protocol(): string {
return this._protocol;
}

get negotiated(): boolean {
return this._negotiated;
}

get readyState(): string {
return this._readyState;
}

send(filepath, position, length) {
WebRTCModule.dataChannelSend(
this._peerConnectionId,
this._reactTag,
filepath,
position,
length
);
}

close() {
if (this._readyState === 'closing' || this._readyState === 'closed') {
return;
}
WebRTCModule.dataChannelClose(this._peerConnectionId, this._reactTag);
}

_unregisterEvents() {
this._subscriptions.forEach(e => e.remove());
this._subscriptions = [];
}

_registerEvents() {
this._subscriptions = [
EventEmitter.addListener('dataChannelStateChanged', ev => {
if (ev.reactTag !== this._reactTag) {
return;
}
this._readyState = ev.state;
if (this._id === null && ev.id !== -1) {
this._id = ev.id;
}
if (this._readyState === 'open') {
this.dispatchEvent(new RTCDataChannelEvent('open', {channel: this}));
} else if (this._readyState === 'closing') {
this.dispatchEvent(new RTCDataChannelEvent('closing', {channel: this}));
} else if (this._readyState === 'closed') {
this.dispatchEvent(new RTCDataChannelEvent('close', {channel: this}));
this._unregisterEvents();
WebRTCModule.dataChannelDispose(this._peerConnectionId, this._reactTag);
}
}),

EventEmitter.addListener('onBufferedAmountChange', ev=> {
this.bufferedAmount = ev.amount;
}),
EventEmitter.addListener('dataChannelReceiveMessage', ev => {
if (ev.reactTag !== this._reactTag) {
return;
}
let data = ev.data;
this.dispatchEvent(new MessageEvent('message', {data}));
}),
];
}
}
Original file line number Diff line number Diff line change
@@ -53,7 +53,9 @@ public String dataChannelStateString(DataChannel.State dataChannelState) {

@Override
public void onBufferedAmountChange(long amount) {
// TODO.
WritableMap params = Arguments.createMap();
params.putInt("amount", (int) mDataChannel.bufferedAmount());
webRTCModule.sendEvent("onBufferedAmountChange", params);
}

@Override
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.oney.WebRTCModule;

import android.net.Uri;
import android.util.Base64;
import android.util.Log;
import android.util.SparseArray;
@@ -8,6 +9,7 @@

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
@@ -25,6 +27,9 @@
import org.webrtc.SessionDescription;
import org.webrtc.VideoTrack;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -48,9 +53,11 @@ class PeerConnectionObserver implements PeerConnection.Observer {
final Map<String, MediaStreamTrack> remoteTracks;
private final VideoTrackAdapter videoTrackAdapters;
private final WebRTCModule webRTCModule;

PeerConnectionObserver(WebRTCModule webRTCModule, int id) {
private InputStream inputStream;
private ReactApplicationContext mReactContext = null;
PeerConnectionObserver(WebRTCModule webRTCModule, int id, ReactApplicationContext reactContext) {
this.webRTCModule = webRTCModule;
this.mReactContext = reactContext;
this.id = id;
this.dataChannels = new HashMap<>();
this.remoteStreams = new HashMap<>();
@@ -212,25 +219,57 @@ void dataChannelDispose(String reactTag) {
dataChannels.remove(reactTag);
}

void dataChannelSend(String reactTag, String data, String type) {
DataChannelWrapper dcw = dataChannels.get(reactTag);
if (dcw == null) {
Log.d(TAG, "dataChannelSend() dataChannel is null");
return;
private Uri getFileUri(String filepath, boolean isDirectoryAllowed) {
Uri uri = Uri.parse(filepath);
if (uri.getScheme() == null) {
// No prefix, assuming that provided path is absolute path to file
File file = new File(filepath);
if (!isDirectoryAllowed && file.isDirectory()) {
System.out.println("PATH ERROR");
}
uri = Uri.parse("file://" + filepath);
}
return uri;
}

byte[] byteArray;
if (type.equals("text")) {
byteArray = data.getBytes(StandardCharsets.UTF_8);
} else if (type.equals("binary")) {
byteArray = Base64.decode(data, Base64.NO_WRAP);
} else {
Log.e(TAG, "Unsupported data type: " + type);
return;

private InputStream getInputStream(String filepath) {
Uri uri = getFileUri(filepath, false);
InputStream stream = null;
try {
stream = this.mReactContext.getContentResolver().openInputStream(uri);

} catch (FileNotFoundException ex) {
System.out.println(ex.getMessage());
}
if (stream == null) {
System.out.println("NO STREAM!");
}
return stream;
}


void dataChannelSend(String reactTag, String filepath, int position, int length) {
try {
DataChannelWrapper dcw = dataChannels.get(reactTag);
if (dcw == null) {
Log.d(TAG, "dataChannelSend() dataChannel is null");
return;
}
if(this.inputStream == null || position == 0) {
this.inputStream = getInputStream(filepath);
inputStream.skip(position);
}
byte[] arrayBuffer = new byte[length];

int bytesRead = inputStream.read(arrayBuffer, 0, length);
ByteBuffer byteBuffer = ByteBuffer.wrap(arrayBuffer);
DataChannel.Buffer buffer = new DataChannel.Buffer(byteBuffer, true);
dcw.getDataChannel().send(buffer);

}catch (Exception ex) {
System.out.println(ex.getMessage());
}
ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
DataChannel.Buffer buffer = new DataChannel.Buffer(byteBuffer, type.equals("binary"));
dcw.getDataChannel().send(buffer);
}

void getStats(Promise promise) {
41 changes: 26 additions & 15 deletions android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import android.content.Context;
import android.util.Log;
import android.util.SparseArray;

@@ -44,6 +45,7 @@ public class WebRTCModule extends ReactContextBaseJavaModule {
// Need to expose the peer connection codec factories here to get capabilities
private final SparseArray<PeerConnectionObserver> mPeerConnectionObservers;
final Map<String, MediaStream> localStreams;
private ReactApplicationContext mReactContext;

private final GetUserMediaImpl getUserMediaImpl;

@@ -83,7 +85,7 @@ public WebRTCModule(ReactApplicationContext reactContext) {

public WebRTCModule(ReactApplicationContext reactContext, Options options) {
super(reactContext);

mReactContext = reactContext;
mPeerConnectionObservers = new SparseArray<>();
localStreams = new HashMap<>();

@@ -408,7 +410,7 @@ public void peerConnectionInit(ReadableMap configuration, int id) {

try {
ThreadUtils.submitToExecutor(() -> {
PeerConnectionObserver observer = new PeerConnectionObserver(this, id);
PeerConnectionObserver observer = new PeerConnectionObserver(this, id, mReactContext);
PeerConnection peerConnection = mFactory.createPeerConnection(rtcConfiguration, observer);
observer.setPeerConnection(peerConnection);
mPeerConnectionObservers.put(id, observer);
@@ -1257,22 +1259,30 @@ public void dataChannelDispose(int peerConnectionId, String reactTag) {
@ReactMethod
public void dataChannelSend(int peerConnectionId,
String reactTag,
String data,
String type) {
ThreadUtils.runOnExecutor(() -> {
// Forward to PeerConnectionObserver which deals with DataChannels
// because DataChannel is owned by PeerConnection.
PeerConnectionObserver pco = mPeerConnectionObservers.get(peerConnectionId);
if (pco == null || pco.getPeerConnection() == null) {
Log.d(TAG, "dataChannelSend() peerConnection is null");
return;
}
String filepath,
int position,
int length) {
ThreadUtils.runOnExecutor(() ->
dataChannelSendAsync(peerConnectionId, reactTag, filepath, position, length));
}

pco.dataChannelSend(reactTag, data, type);
});
private void dataChannelSendAsync(int peerConnectionId,
String reactTag,
String filepath,
int position,
int length) {
// Forward to PeerConnectionObserver which deals with DataChannels
// because DataChannel is owned by PeerConnection.
PeerConnectionObserver pco = mPeerConnectionObservers.get(peerConnectionId);
if (pco == null || pco.getPeerConnection() == null) {
Log.d(TAG, "dataChannelSend() peerConnection is null");
return;
}

pco.dataChannelSend(reactTag, filepath, position, length );
}

@ReactMethod
@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}
@@ -1281,4 +1291,5 @@ public void addListener(String eventName) {
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}

}
Loading