@skyway-sdk/core
Version:
The official Next Generation JavaScript SDK for SkyWay
295 lines • 14.4 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.P2PConnection = void 0;
const common_1 = require("@skyway-sdk/common");
const uuid_1 = require("uuid");
const errors_1 = require("../../../../errors");
const publication_1 = require("../../../../publication");
const util_1 = require("../../../../util");
const receiver_1 = require("./receiver");
const sender_1 = require("./sender");
const log = new common_1.Logger('packages/core/src/plugin/internal/person/connection/index.ts');
/**@internal */
class P2PConnection {
/**@internal */
constructor(_iceManager, _signaling, _analytics, _context, channelId, localPerson, remoteMember) {
this._iceManager = _iceManager;
this._signaling = _signaling;
this._analytics = _analytics;
this._context = _context;
this.channelId = channelId;
this.localPerson = localPerson;
this.remoteMember = remoteMember;
this.id = (0, uuid_1.v4)();
this.type = 'p2p';
this.onDisconnect = new common_1.Event();
this.onClose = new common_1.Event();
this.closed = false;
this.disconnected = false;
this._log = log.createBlock({
id: this.id,
localPersonId: this.localPerson.id,
});
this._pubsubQueue = new common_1.PromiseQueue();
this.sendSubscriptionStatsReportTimers = new Map();
this._waitingSendSubscriptionStatsReportsFromPublish = new Map();
this._waitingSendSubscriptionStatsReportsFromSubscribe = [];
this.sender = new sender_1.Sender(this._context, this._iceManager, this._signaling, this._analytics, this.localPerson, this.remoteMember);
this.receiver = new receiver_1.Receiver(this._context, this._iceManager, this._signaling, this._analytics, this.localPerson, this.remoteMember);
this.sender.onDisconnect.once(() => {
this.disconnected = true;
this.onDisconnect.emit();
});
this.receiver.onDisconnect.once(() => {
this.disconnected = true;
this.onDisconnect.emit();
});
if (this._analytics) {
this._analytics.onConnectionStateChanged.add((state) => {
// AnalyticsServerに初回接続できなかった場合のsendSubscriptionStatsReportタイマー再セット処理
if (state !== 'connected')
return;
if (this._waitingSendSubscriptionStatsReportsFromPublish.size > 0) {
for (const [subscriptionId, publicationId] of this
._waitingSendSubscriptionStatsReportsFromPublish) {
const publication = this.sender.publications[publicationId];
if (publication) {
this.startSendSubscriptionStatsReportTimer(publication, subscriptionId);
}
}
this._waitingSendSubscriptionStatsReportsFromPublish.clear();
}
if (this._waitingSendSubscriptionStatsReportsFromSubscribe.length > 0) {
for (const subscriptionId of this
._waitingSendSubscriptionStatsReportsFromSubscribe) {
const subscription = this.receiver.subscriptions[subscriptionId];
if (subscription) {
this.startSendSubscriptionStatsReportTimer(subscription, subscriptionId);
}
}
this._waitingSendSubscriptionStatsReportsFromSubscribe = [];
}
});
}
}
/**
* @internal
* @throws {SkyWayError}
*/
startPublishing(publication, subscriptionId) {
return __awaiter(this, void 0, void 0, function* () {
yield this._pubsubQueue.push(() => __awaiter(this, void 0, void 0, function* () {
this._log.debug('startPublishing', { publication });
yield this.sender.add(publication);
}));
if (this._analytics && !this._analytics.isClosed()) {
// 再送時に他の処理をブロックしないためにawaitしない
void this._analytics.client.sendBindingRtcPeerConnectionToSubscription({
subscriptionId: subscriptionId,
role: 'sender',
rtcPeerConnectionId: this.sender.rtcPeerConnectionId,
});
if (this._analytics.client.isConnectionEstablished()) {
this.startSendSubscriptionStatsReportTimer(publication, subscriptionId);
}
else {
// AnalyticsServerに初回接続できなかった場合はキューに入れる
this._waitingSendSubscriptionStatsReportsFromPublish.set(subscriptionId, publication.id);
}
}
});
}
/**@internal */
stopPublishing(publication) {
return __awaiter(this, void 0, void 0, function* () {
yield this._pubsubQueue.push(() => __awaiter(this, void 0, void 0, function* () {
this._log.debug('<stopPublishing> start', { publication });
this.sender
.remove(publication.id)
.then(() => {
this._log.debug('<stopPublishing> removed', { publication });
})
.catch((e) => {
this._log.error('<stopPublishing> remove failed', e, { publication });
});
this._closeIfNeeded();
this._log.debug('<stopPublishing> end', { publication });
}));
// publication(=stream)のidをkeyとして一致するタイマーを取得する
const sendSubscriptionStatsReportTimer = this.sendSubscriptionStatsReportTimers.get(publication.id);
if (sendSubscriptionStatsReportTimer) {
clearInterval(sendSubscriptionStatsReportTimer);
this.sendSubscriptionStatsReportTimers.delete(publication.id);
}
});
}
/**@internal */
startSubscribing(subscription) {
return __awaiter(this, void 0, void 0, function* () {
yield this._pubsubQueue.push(() => __awaiter(this, void 0, void 0, function* () {
this._log.debug('startSubscribing', { subscription });
this.receiver.add(subscription);
const publicationId = subscription.publication.id;
let stream = this.receiver.streams[publicationId];
if (!stream) {
yield this.receiver.onStreamAdded
.watch((res) => res.publicationId === publicationId, this._context.config.rtcConfig.timeout)
.catch(() => {
throw (0, util_1.createError)({
operationName: 'P2PConnection.startSubscribing',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onStreamAdded' }),
path: log.prefix,
context: this._context,
channel: this.localPerson.channel,
payload: { subscription },
});
});
stream = this.receiver.streams[publicationId];
}
stream.setIsEnabled(subscription.publication.state === 'enabled');
subscription.codec = stream.codec;
subscription._setStream(stream);
if (this._analytics && !this._analytics.isClosed()) {
// 再送時に他の処理をブロックしないためにawaitしない
void this._analytics.client.sendBindingRtcPeerConnectionToSubscription({
subscriptionId: subscription.id,
role: 'receiver',
rtcPeerConnectionId: this.receiver.rtcPeerConnectionId,
});
if (this._analytics.client.isConnectionEstablished()) {
this.startSendSubscriptionStatsReportTimer(subscription, subscription.id);
}
else {
// AnalyticsServerに初回接続できなかった場合はキューに入れる
this._waitingSendSubscriptionStatsReportsFromSubscribe.push(subscription.id);
}
}
}));
});
}
/**@internal */
stopSubscribing(subscription) {
return __awaiter(this, void 0, void 0, function* () {
yield this._pubsubQueue.push(() => __awaiter(this, void 0, void 0, function* () {
this._log.debug('stopSubscribing', { subscription });
this.receiver.remove(subscription.id);
this._closeIfNeeded();
}));
// subscription(=stream)のidをkeyとして一致するタイマーを取得する
const sendSubscriptionStatsReportTimer = this.sendSubscriptionStatsReportTimers.get(subscription.id);
if (sendSubscriptionStatsReportTimer) {
clearInterval(sendSubscriptionStatsReportTimer);
this.sendSubscriptionStatsReportTimers.delete(subscription.id);
}
});
}
_closeIfNeeded() {
if (this.sender.hasMedia || this.receiver.hasMedia)
return;
this.close({ reason: 'no media' });
}
getStats(content) {
return __awaiter(this, void 0, void 0, function* () {
const stream = content.stream;
if (!stream) {
throw (0, util_1.createError)({
operationName: 'P2PConnection.getStats',
info: Object.assign(Object.assign({}, errors_1.errors.invalidArgumentValue), { detail: 'Subscription or Publication must has stream' }),
path: log.prefix,
context: this._context,
channel: this.localPerson.channel,
});
}
if (stream.side === 'local') {
if (stream.contentType === 'data') {
return this.sender.pc.getStats();
}
return this.sender.pc.getStats(stream.track);
}
else {
if (stream.contentType === 'data') {
return this.receiver.pc.getStats();
}
return this.receiver.pc.getStats(stream.track);
}
});
}
/**@internal */
close({ reason } = {}) {
if (this.closed) {
return;
}
this.closed = true;
this._log.debug('closed', {
endpointId: this.remoteMember.id,
reason,
sender: this.sender.id,
receiver: this.receiver.id,
id: this.id,
});
this.sender.close();
this.receiver.close();
for (const timer of this.sendSubscriptionStatsReportTimers.values()) {
clearInterval(timer);
}
this.sendSubscriptionStatsReportTimers.clear();
this._waitingSendSubscriptionStatsReportsFromPublish.clear();
this._waitingSendSubscriptionStatsReportsFromSubscribe = [];
this.onClose.emit();
}
startSendSubscriptionStatsReportTimer(stream, subscriptionId) {
if (this._analytics) {
const role = stream instanceof publication_1.PublicationImpl ? 'sender' : 'receiver';
const intervalSec = this._analytics.client.getIntervalSec();
this.sendSubscriptionStatsReportTimers.set(stream.id, setInterval(() => __awaiter(this, void 0, void 0, function* () {
if (!this._analytics) {
throw (0, util_1.createError)({
operationName: 'P2PConnection.sendSubscriptionStatsReportTimer',
info: Object.assign(Object.assign({}, errors_1.errors.missingProperty), { detail: 'AnalyticsSession not exist' }),
path: log.prefix,
context: this._context,
channel: this.localPerson.channel,
});
}
// AnalyticsSessionがcloseされていたらタイマーを止める
if (this._analytics.isClosed()) {
const subscriptionStatsReportTimer = this.sendSubscriptionStatsReportTimers.get(stream.id);
if (subscriptionStatsReportTimer) {
clearInterval(subscriptionStatsReportTimer);
this.sendSubscriptionStatsReportTimers.delete(stream.id);
}
return;
}
const stats = yield this.getStats(stream);
if (stats) {
// 再送時に他の処理をブロックしないためにawaitしない
void this._analytics.client.sendSubscriptionStatsReport(stats, {
subscriptionId: subscriptionId,
role: role,
contentType: stream.contentType,
createdAt: Date.now(),
});
}
}), intervalSec * 1000));
}
}
}
exports.P2PConnection = P2PConnection;
const p2pMessageKinds = [
'senderProduceMessage',
'senderUnproduceMessage',
'receiverAnswerMessage',
'iceCandidateMessage',
'senderRestartIceMessage',
'ping',
];
//# sourceMappingURL=index.js.map