@skyway-sdk/sfu-bot
Version:
The official Next Generation JavaScript SDK for SkyWay
238 lines • 11.5 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.Receiver = void 0;
const common_1 = require("@skyway-sdk/common");
const core_1 = require("@skyway-sdk/core");
const core_2 = require("@skyway-sdk/core");
const errors_1 = require("../errors");
const util_1 = require("../util");
const log = new common_1.Logger('packages/sfu-bot/src/connection/receiver.ts');
class Receiver {
constructor(subscription, _api, _transportRepository, _localPerson, _bot, _iceManager, _context) {
this.subscription = subscription;
this._api = _api;
this._transportRepository = _transportRepository;
this._localPerson = _localPerson;
this._bot = _bot;
this._iceManager = _iceManager;
this._context = _context;
this._disposer = new common_1.EventDisposer();
this.sendSubscriptionStatsReportTimer = null;
this._waitingSendSubscriptionStatsReports = [];
const analyticsSession = this._localPerson._analytics;
if (analyticsSession) {
analyticsSession.onConnectionStateChanged.add((state) => {
if (state === 'connected' &&
this._waitingSendSubscriptionStatsReports.length > 0) {
for (const consumerId of this._waitingSendSubscriptionStatsReports) {
if (this.consumer && this.consumer.id === consumerId) {
this.startSendSubscriptionStatsReportTimer();
}
}
this._waitingSendSubscriptionStatsReports = [];
}
});
}
}
toJSON() {
return {
transport: this.transport,
subscription: this.subscription,
};
}
/**@throws {maxSubscriberExceededError} */
consume() {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
let rtpCapabilities = this._transportRepository.rtpCapabilities;
if (!rtpCapabilities) {
log.debug('[start] getCapabilities');
rtpCapabilities = yield this._api.getRtpCapabilities({
botId: this._bot.id,
forwardingId: this.subscription.publication.id,
originPublicationId: this.subscription.publication.origin.id,
});
log.debug('[end] getCapabilities');
yield this._transportRepository.loadDevice(rtpCapabilities).catch((e) => {
throw (0, core_1.createError)({
operationName: 'Receiver.consume',
context: this._context,
channel: this._localPerson.channel,
info: Object.assign(Object.assign({}, errors_1.errors.internal), { detail: 'sfu loadDevice failed' }),
path: log.prefix,
error: e,
});
});
}
const spatialLayer = this.subscription.preferredEncoding
? (0, util_1.getLayerFromEncodings)(this.subscription.preferredEncoding, (_b = (_a = this.subscription.publication.origin) === null || _a === void 0 ? void 0 : _a.encodings) !== null && _b !== void 0 ? _b : [])
: undefined;
log.debug('[start] createConsumer', { subscription: this.subscription });
const { consumerOptions, transportOptions, transportId, producerId } = yield this._api.createConsumer({
botId: this._bot.id,
forwardingId: this.subscription.publication.id,
rtpCapabilities,
subscriptionId: this.subscription.id,
subscriberId: this.subscription.subscriber.id,
spatialLayer,
originPublicationId: this.subscription.publication.origin.id,
});
if (transportOptions) {
this._transportRepository.createTransport(this._localPerson.id, this._bot, transportOptions, 'recv', this._iceManager, this._localPerson._analytics);
}
this.transport = this._transportRepository.getTransport(this._localPerson.id, transportId);
if (!this.transport) {
log.warn('transport is under race condition', { transportId });
yield this._transportRepository.onTransportCreated
.watch((id) => id === transportId, this._bot.options.endpointTimeout)
.catch((e) => {
throw (0, core_1.createError)({
operationName: 'Receiver.consume',
context: this._context,
channel: this._localPerson.channel,
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'receiver sfuTransport not found' }),
path: log.prefix,
error: e,
payload: {
transportOptions,
transportId,
producerId,
consumerOptions,
subscription: this.subscription,
},
});
});
this.transport = this._transportRepository.getTransport(this._localPerson.id, transportId);
}
if (this._localPerson._analytics &&
!this._localPerson._analytics.isClosed()) {
// 再送時に他の処理をブロックしないためにawaitしない
void this._localPerson._analytics.client.sendBindingRtcPeerConnectionToSubscription({
subscriptionId: this.subscription.id,
role: 'receiver',
rtcPeerConnectionId: this.transport.id,
});
}
log.debug('[end] createConsumer');
log.debug('[start] consume', {
consumerOptions,
subscription: this.subscription,
});
const consumer = yield this.transport.msTransport
.consume(Object.assign(Object.assign({}, consumerOptions), { producerId }))
.catch((e) => {
throw (0, core_1.createError)({
operationName: 'Receiver.consume',
context: this._context,
channel: this._localPerson.channel,
info: Object.assign(Object.assign({}, errors_1.errors.internal), { detail: 'consume failed, maybe subscribing unsupported codec' }),
path: log.prefix,
error: e,
});
});
this.consumer = consumer;
log.debug('[end] consume', { subscription: this.subscription });
const [selectedCodec] = consumer.rtpParameters.codecs;
const stream = (0, core_2.createRemoteStream)((0, core_1.uuidV4)(), consumer.track, selectedCodec);
const codec = {
mimeType: selectedCodec.mimeType,
parameters: selectedCodec.parameters,
};
this._setupTransportAccessForStream(stream, consumer);
const analyticsSession = this._localPerson._analytics;
if (analyticsSession && !analyticsSession.isClosed()) {
if (analyticsSession.client.isConnectionEstablished()) {
this.startSendSubscriptionStatsReportTimer();
}
else {
// AnalyticsServerに初回接続できなかった場合はキューに入れる
this._waitingSendSubscriptionStatsReports.push(consumer.id);
}
}
return { stream, codec };
});
}
_setupTransportAccessForStream(stream, consumer) {
const transport = this.transport;
const pc = this.pc;
stream._getTransport = () => ({
rtcPeerConnection: pc,
connectionState: transport.connectionState,
info: this,
});
stream._getStats = () => __awaiter(this, void 0, void 0, function* () {
const stats = yield consumer.getStats();
let arr = (0, core_1.statsToArray)(stats);
arr = arr.map((stats) => {
stats['sfuTransportId'] = transport.id;
return stats;
});
return arr;
});
this._disposer.push(() => {
stream._getTransport = () => undefined;
});
transport.onConnectionStateChanged.add((state) => {
log.debug('transport connection state changed', transport.id, state);
stream._setConnectionState(state);
});
}
unconsume() {
if (!this.consumer) {
log.debug('unconsume failed, consumer not exist', {
subscription: this.subscription,
});
return;
}
this.consumer.close();
this.consumer = undefined;
if (this.sendSubscriptionStatsReportTimer) {
clearInterval(this.sendSubscriptionStatsReportTimer);
}
}
close() {
this._disposer.dispose();
}
get pc() {
var _a;
return (_a = this.transport) === null || _a === void 0 ? void 0 : _a.pc;
}
startSendSubscriptionStatsReportTimer() {
const analyticsSession = this._localPerson._analytics;
if (analyticsSession) {
const intervalSec = analyticsSession.client.getIntervalSec();
this.sendSubscriptionStatsReportTimer = setInterval(() => __awaiter(this, void 0, void 0, function* () {
// AnalyticsSessionがcloseされていたらタイマーを止める
if (!analyticsSession || analyticsSession.isClosed()) {
if (this.sendSubscriptionStatsReportTimer) {
clearInterval(this.sendSubscriptionStatsReportTimer);
}
return;
}
if (this.consumer) {
const stats = yield this.consumer.getStats();
if (stats) {
// 再送時に他の処理をブロックしないためにawaitしない
void analyticsSession.client.sendSubscriptionStatsReport(stats, {
subscriptionId: this.subscription.id,
role: 'receiver',
contentType: this.subscription.contentType,
createdAt: Date.now(),
});
}
}
}), intervalSec * 1000);
}
}
}
exports.Receiver = Receiver;
//# sourceMappingURL=receiver.js.map