UNPKG

@skyway-sdk/sfu-bot

Version:

The official Next Generation JavaScript SDK for SkyWay

238 lines 11.5 kB
"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