3 Star 0 Fork 1

mirrors_mattermost/react-native-webrtc

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
RTCPeerConnection.js 15.11 KB
一键复制 编辑 原始数据 按行查看 历史
'use strict';
import EventTarget from 'event-target-shim';
import {DeviceEventEmitter, NativeModules} from 'react-native';
import MediaStream from './MediaStream';
import MediaStreamEvent from './MediaStreamEvent';
import MediaStreamTrack from './MediaStreamTrack';
import MediaStreamTrackEvent from './MediaStreamTrackEvent';
import RTCDataChannel from './RTCDataChannel';
import RTCDataChannelEvent from './RTCDataChannelEvent';
import RTCSessionDescription from './RTCSessionDescription';
import RTCIceCandidate from './RTCIceCandidate';
import RTCIceCandidateEvent from './RTCIceCandidateEvent';
import RTCEvent from './RTCEvent';
import RTCRtpTransceiver from './RTCRtpTransceiver';
import * as RTCUtil from './RTCUtil';
const {WebRTCModule} = NativeModules;
type RTCSignalingState =
'stable' |
'have-local-offer' |
'have-remote-offer' |
'have-local-pranswer' |
'have-remote-pranswer' |
'closed';
type RTCIceGatheringState =
'new' |
'gathering' |
'complete';
type RTCIceConnectionState =
'new' |
'checking' |
'connected' |
'completed' |
'failed' |
'disconnected' |
'closed';
const PEER_CONNECTION_EVENTS = [
'connectionstatechange',
'icecandidate',
'icecandidateerror',
'iceconnectionstatechange',
'icegatheringstatechange',
'negotiationneeded',
'signalingstatechange',
// Peer-to-peer Data API:
'datachannel',
// old:
'addstream',
'removestream',
];
let nextPeerConnectionId = 0;
export default class RTCPeerConnection extends EventTarget(PEER_CONNECTION_EVENTS) {
localDescription: RTCSessionDescription;
remoteDescription: RTCSessionDescription;
signalingState: RTCSignalingState = 'stable';
iceGatheringState: RTCIceGatheringState = 'new';
iceConnectionState: RTCIceConnectionState = 'new';
onconnectionstatechange: ?Function;
onicecandidate: ?Function;
onicecandidateerror: ?Function;
oniceconnectionstatechange: ?Function;
onicegatheringstatechange: ?Function;
onnegotiationneeded: ?Function;
onsignalingstatechange: ?Function;
onaddstream: ?Function;
onremovestream: ?Function;
_peerConnectionId: number;
_localStreams: Array<MediaStream> = [];
_remoteStreams: Array<MediaStream> = [];
_subscriptions: Array<any>;
_transceivers: Array<RTCRtpTransceiver> = [];
/**
* The RTCDataChannel.id allocator of this RTCPeerConnection.
*/
_dataChannelIds: Set = new Set();
constructor(configuration) {
super();
this._peerConnectionId = nextPeerConnectionId++;
WebRTCModule.peerConnectionInit(configuration, this._peerConnectionId);
this._registerEvents();
}
addStream(stream: MediaStream) {
const index = this._localStreams.indexOf(stream);
if (index !== -1) {
return;
}
WebRTCModule.peerConnectionAddStream(stream._reactTag, this._peerConnectionId);
this._localStreams.push(stream);
}
removeStream(stream: MediaStream) {
const index = this._localStreams.indexOf(stream);
if (index === -1) {
return;
}
this._localStreams.splice(index, 1);
WebRTCModule.peerConnectionRemoveStream(stream._reactTag, this._peerConnectionId);
}
addTransceiver(source: 'audio' |'video' | MediaStreamTrack, init) {
return new Promise((resolve, reject) => {
let src;
if (source === 'audio') {
src = { type: 'audio' };
} else if (source === 'video') {
src = { type: 'video' };
} else {
src = { trackId: source.id };
}
WebRTCModule.peerConnectionAddTransceiver(this._peerConnectionId, {...src, init: { ...init } }, (successful, data) => {
if (successful) {
this._mergeState(data.state);
resolve(this._transceivers.find((v) => v.id === data.id));
} else {
reject(data);
}
});
});
};
createOffer(options) {
return new Promise((resolve, reject) => {
WebRTCModule.peerConnectionCreateOffer(
this._peerConnectionId,
RTCUtil.normalizeOfferAnswerOptions(options),
(successful, data) => {
if (successful) {
this._mergeState(data.state);
resolve(new RTCSessionDescription(data.session));
} else {
reject(data); // TODO: convert to NavigatorUserMediaError
}
});
});
}
createAnswer(options = {}) {
return new Promise((resolve, reject) => {
WebRTCModule.peerConnectionCreateAnswer(
this._peerConnectionId,
RTCUtil.normalizeOfferAnswerOptions(options),
(successful, data) => {
if (successful) {
this._mergeState(data.state);
resolve(new RTCSessionDescription(data.session));
} else {
reject(data);
}
});
});
}
setConfiguration(configuration) {
WebRTCModule.peerConnectionSetConfiguration(configuration, this._peerConnectionId);
}
setLocalDescription(sessionDescription: RTCSessionDescription) {
return new Promise((resolve, reject) => {
WebRTCModule.peerConnectionSetLocalDescription(
sessionDescription.toJSON ? sessionDescription.toJSON() : sessionDescription,
this._peerConnectionId,
(successful, data) => {
if (successful) {
this.localDescription = sessionDescription;
this._mergeState(data.state);
resolve();
} else {
reject(data);
}
});
});
}
setRemoteDescription(sessionDescription: RTCSessionDescription) {
return new Promise((resolve, reject) => {
WebRTCModule.peerConnectionSetRemoteDescription(
sessionDescription.toJSON ? sessionDescription.toJSON() : sessionDescription,
this._peerConnectionId,
(successful, data) => {
if (successful) {
this.remoteDescription = sessionDescription;
this._mergeState(data.state);
resolve();
} else {
reject(data);
}
});
});
}
addIceCandidate(candidate) {
return new Promise((resolve, reject) => {
WebRTCModule.peerConnectionAddICECandidate(
candidate.toJSON ? candidate.toJSON() : candidate,
this._peerConnectionId,
(successful) => {
if (successful) {
resolve()
} else {
// XXX: This should be OperationError
reject(new Error('Failed to add ICE candidate'));
}
});
});
}
getStats(track) {
// NOTE: This returns a Promise but the format of the results is still
// the "legacy" one. The native side (in Oobj-C) doesn't yet support the
// new format: https://bugs.chromium.org/p/webrtc/issues/detail?id=6872
return new Promise((resolve, reject) => {
WebRTCModule.peerConnectionGetStats(
(track && track.id) || '',
this._peerConnectionId,
(success, data) => {
if (success) {
// On both Android and iOS it is faster to construct a single
// JSON string representing the array of StatsReports and have it
// pass through the React Native bridge rather than the array of
// StatsReports. While the implementations do try to be faster in
// general, the stress is on being faster to pass through the React
// Native bridge which is a bottleneck that tends to be visible in
// the UI when there is congestion involving UI-related passing.
try {
const stats = JSON.parse(data);
resolve(stats);
} catch (e) {
reject(e);
}
} else {
reject(new Error(data));
}
});
});
}
getLocalStreams() {
return this._localStreams.slice();
}
getRemoteStreams() {
return this._remoteStreams.slice();
}
getTransceivers() {
return this._transceivers.slice();
}
close(cb?: () => void) {
if (!cb) {
cb = () => null;
}
WebRTCModule.peerConnectionClose(this._peerConnectionId, cb);
}
_getTrack(streamReactTag, trackId): MediaStreamTrack {
const stream
= this._remoteStreams.find(
stream => stream._reactTag === streamReactTag);
return stream && stream._tracks.find(track => track.id === trackId);
}
_getTransceiver(state): RTCRtpTransceiver {
const existing = this._transceivers.find((t) => t.id === state.id);
if (existing) {
existing._updateState(state);
return existing;
} else {
let res = new RTCRtpTransceiver(this._peerConnectionId, state, (s) => this._mergeState(s));
this._transceivers.push(res);
return res;
}
}
_mergeState(state): void {
if (!state) {
return;
}
// Merge Transceivers states
if (state.transceivers) {
// Apply states
for(let transceiver of state.transceivers) {
this._getTransceiver(transceiver);
}
// Restore Order
this._transceivers =
this._transceivers.map((t, i) => this._transceivers.find((t2) => t2.id === state.transceivers[i].id));
}
}
_unregisterEvents(): void {
this._subscriptions.forEach(e => e.remove());
this._subscriptions = [];
}
_registerEvents(): void {
this._subscriptions = [
DeviceEventEmitter.addListener('peerConnectionOnRenegotiationNeeded', ev => {
if (ev.id !== this._peerConnectionId) {
return;
}
this.dispatchEvent(new RTCEvent('negotiationneeded'));
}),
DeviceEventEmitter.addListener('peerConnectionIceConnectionChanged', ev => {
if (ev.id !== this._peerConnectionId) {
return;
}
this.iceConnectionState = ev.iceConnectionState;
this.dispatchEvent(new RTCEvent('iceconnectionstatechange'));
if (ev.iceConnectionState === 'closed') {
// This PeerConnection is done, clean up event handlers.
this._unregisterEvents();
}
}),
DeviceEventEmitter.addListener('peerConnectionSignalingStateChanged', ev => {
if (ev.id !== this._peerConnectionId) {
return;
}
this.signalingState = ev.signalingState;
this.dispatchEvent(new RTCEvent('signalingstatechange'));
}),
DeviceEventEmitter.addListener('peerConnectionAddedStream', ev => {
if (ev.id !== this._peerConnectionId) {
return;
}
const stream = new MediaStream(ev);
this._remoteStreams.push(stream);
this.dispatchEvent(new MediaStreamEvent('addstream', {stream}));
}),
DeviceEventEmitter.addListener('peerConnectionRemovedStream', ev => {
if (ev.id !== this._peerConnectionId) {
return;
}
const stream = this._remoteStreams.find(s => s._reactTag === ev.streamId);
if (stream) {
const index = this._remoteStreams.indexOf(stream);
if (index !== -1) {
this._remoteStreams.splice(index, 1);
}
}
this.dispatchEvent(new MediaStreamEvent('removestream', {stream}));
}),
DeviceEventEmitter.addListener('mediaStreamTrackMuteChanged', ev => {
if (ev.peerConnectionId !== this._peerConnectionId) {
return;
}
const track = this._getTrack(ev.streamReactTag, ev.trackId);
if (track) {
track.muted = ev.muted;
const eventName = ev.muted ? 'mute' : 'unmute';
track.dispatchEvent(new MediaStreamTrackEvent(eventName, {track}));
}
}),
DeviceEventEmitter.addListener('peerConnectionGotICECandidate', ev => {
if (ev.id !== this._peerConnectionId) {
return;
}
const candidate = new RTCIceCandidate(ev.candidate);
const event = new RTCIceCandidateEvent('icecandidate', {candidate});
this.dispatchEvent(event);
}),
DeviceEventEmitter.addListener('peerConnectionIceGatheringChanged', ev => {
if (ev.id !== this._peerConnectionId) {
return;
}
this.iceGatheringState = ev.iceGatheringState;
if (this.iceGatheringState === 'complete') {
this.dispatchEvent(new RTCIceCandidateEvent('icecandidate', null));
}
this.dispatchEvent(new RTCEvent('icegatheringstatechange'));
}),
DeviceEventEmitter.addListener('peerConnectionDidOpenDataChannel', ev => {
if (ev.id !== this._peerConnectionId) {
return;
}
const evDataChannel = ev.dataChannel;
const id = evDataChannel.id;
// XXX RTP data channels are not defined by the WebRTC standard, have
// been deprecated in Chromium, and Google have decided (in 2015) to no
// longer support them (in the face of multiple reported issues of
// breakages).
if (typeof id !== 'number' || id === -1) {
return;
}
const channel
= new RTCDataChannel(
this._peerConnectionId,
evDataChannel.label,
evDataChannel);
// XXX webrtc::PeerConnection checked that id was not in use in its own
// SID allocator before it invoked us. Additionally, its own SID
// allocator is the authority on ResourceInUse. Consequently, it is
// (pretty) safe to update our RTCDataChannel.id allocator without
// checking for ResourceInUse.
this._dataChannelIds.add(id);
this.dispatchEvent(new RTCDataChannelEvent('datachannel', {channel}));
})
];
}
/**
* Creates a new RTCDataChannel object with the given label. The
* RTCDataChannelInit dictionary can be used to configure properties of the
* underlying channel such as data reliability.
*
* @param {string} label - the value with which the label attribute of the new
* instance is to be initialized
* @param {RTCDataChannelInit} dataChannelDict - an optional dictionary of
* values with which to initialize corresponding attributes of the new
* instance such as id
*/
createDataChannel(label: string, dataChannelDict?: ?RTCDataChannelInit) {
let id;
const dataChannelIds = this._dataChannelIds;
if (dataChannelDict && 'id' in dataChannelDict) {
id = dataChannelDict.id;
if (typeof id !== 'number') {
throw new TypeError('DataChannel id must be a number: ' + id);
}
if (dataChannelIds.has(id)) {
throw new ResourceInUse('DataChannel id already in use: ' + id);
}
} else {
// Allocate a new id.
// TODO Remembering the last used/allocated id and then incrementing it to
// generate the next id to use will surely be faster. However, I want to
// reuse ids (in the future) as the RTCDataChannel.id space is limited to
// unsigned short by the standard:
// https://www.w3.org/TR/webrtc/#dom-datachannel-id. Additionally, 65535
// is reserved due to SCTP INIT and INIT-ACK chunks only allowing a
// maximum of 65535 streams to be negotiated (as defined by the WebRTC
// Data Channel Establishment Protocol).
for (id = 1; id < 65535 && dataChannelIds.has(id); ++id);
// TODO Throw an error if no unused id is available.
dataChannelDict = Object.assign({id}, dataChannelDict);
}
WebRTCModule.createDataChannel(
this._peerConnectionId,
label,
dataChannelDict);
dataChannelIds.add(id);
return new RTCDataChannel(this._peerConnectionId, label, dataChannelDict);
}
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/mirrors_mattermost/react-native-webrtc.git
git@gitee.com:mirrors_mattermost/react-native-webrtc.git
mirrors_mattermost
react-native-webrtc
react-native-webrtc
master

搜索帮助