UNPKG

@skyway-sdk/core

Version:

The official Next Generation JavaScript SDK for SkyWay

659 lines 32 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; 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.LocalPersonImpl = void 0; const common_1 = require("@skyway-sdk/common"); const errors_1 = require("../../errors"); const member_1 = require("../../member"); const member_2 = require("../../plugin/internal/unknown/member"); const publication_1 = require("../../publication"); const util_1 = require("../../util"); const remoteMember_1 = require("../remoteMember"); const agent_1 = require("./agent"); __exportStar(require("./adapter"), exports); __exportStar(require("./factory"), exports); const log = new common_1.Logger('packages/core/src/member/localPerson/index.ts'); /**@internal */ class LocalPersonImpl extends member_1.MemberImpl { /**@private */ constructor(args) { super(args); this.args = args; this.type = 'person'; this.subtype = 'person'; this.side = 'local'; this.keepaliveIntervalSec = this.args.keepaliveIntervalSec; this.keepaliveIntervalGapSec = this.args.keepaliveIntervalGapSec; this.preventAutoLeaveOnBeforeUnload = this.args.preventAutoLeaveOnBeforeUnload; this.disableSignaling = this.args.disableSignaling; this.disableAnalytics = this.args.disableAnalytics; this.config = this.context.config; this.onStreamPublished = this._events.make(); this.onStreamUnpublished = this._events.make(); this.onPublicationListChanged = this._events.make(); this.onPublicationSubscribed = this._events.make(); this.onPublicationUnsubscribed = this._events.make(); this.onSubscriptionListChanged = this._events.make(); this.onFatalError = this._events.make(); this._onStreamSubscribeFailed = this._events.make(); /**@private */ this._onDisposed = this._events.make(); this._disposer = new common_1.EventDisposer(); this._subscribing = {}; this._requestQueue = new common_1.PromiseQueue(); /**@private */ this.iceManager = this.args.iceManager; /**@private */ this._disposed = false; this._publishingAgent = new agent_1.PublishingAgent(this); this._subscribingAgent = new agent_1.SubscribingAgent(this); this._signaling = args.signaling; this._analytics = args.analytics; this._listenChannelEvent(); this._listenBeforeUnload(); } static Create(...args) { return __awaiter(this, void 0, void 0, function* () { const person = new LocalPersonImpl(...args); yield person._setupTtlTimer(); if (person._analytics) { void person._analytics.client.sendJoinReport({ memberId: person.id, }); } return person; }); } _listenChannelEvent() { this.channel.onPublicationSubscribed .add(({ subscription }) => __awaiter(this, void 0, void 0, function* () { yield this._handleOnPublicationSubscribe(subscription).catch((e) => log.error('_handleOnStreamSubscribe', e)); })) .disposer(this._disposer); this.channel.onPublicationUnsubscribed .add(({ subscription }) => __awaiter(this, void 0, void 0, function* () { yield this._handleOnPublicationUnsubscribe(subscription).catch((e) => log.error('_handleOnStreamUnsubscribe', e)); })) .disposer(this._disposer); this.channel._onDisposed.once(() => { this.dispose(); }); this.onLeft.once(() => { this.dispose(); }); } /**@throws {@SkyWayError} */ _setupTtlTimer() { return __awaiter(this, void 0, void 0, function* () { const { keepaliveIntervalSec, keepaliveIntervalGapSec } = this; if (keepaliveIntervalSec == null) return; log.debug('_setupTtlTimer', this.toJSON(), { keepaliveIntervalSec, keepaliveIntervalGapSec, }); if (keepaliveIntervalSec === -1) { return; } const updateTtl = () => __awaiter(this, void 0, void 0, function* () { if (this._disposed) { return; } const now = yield this.context._api.getServerUnixtimeInSec(); this.ttlSec = Math.floor(now + keepaliveIntervalSec + (keepaliveIntervalGapSec !== null && keepaliveIntervalGapSec !== void 0 ? keepaliveIntervalGapSec : 0)); try { yield this.channel._updateMemberTtl(this.id, this.ttlSec); log.debug('updateTtl', this.toJSON(), { now, ttlSec: this.ttlSec, keepaliveIntervalSec: keepaliveIntervalSec !== null && keepaliveIntervalSec !== void 0 ? keepaliveIntervalSec : 0, keepaliveIntervalGapSec: keepaliveIntervalGapSec !== null && keepaliveIntervalGapSec !== void 0 ? keepaliveIntervalGapSec : 0, diff: this.ttlSec - now, }); } catch (error) { if (this._disposed) { return; } throw error; } }); yield updateTtl(); this.ttlInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () { yield updateTtl().catch((error) => { if (!this._disposed) { this.onFatalError.emit((0, util_1.createError)({ operationName: 'localPerson._setupTtlTimer', path: log.prefix, info: Object.assign(Object.assign({}, errors_1.errors.internal), { detail: 'updateMemberTtl failed' }), channel: this.channel, context: this.context, error, })); this.dispose(); } }); }), keepaliveIntervalSec * 1000); }); } _listenBeforeUnload() { if (window && !this.preventAutoLeaveOnBeforeUnload) { const leave = () => __awaiter(this, void 0, void 0, function* () { window.removeEventListener('beforeunload', leave); if (this.state !== 'joined') { return; } log.debug('leave by beforeunload', this.toJSON()); yield this.leave(); }); window.addEventListener('beforeunload', leave); } } /**@throws {@link SkyWayError} */ _handleOnPublicationSubscribe(subscription) { var _a; return __awaiter(this, void 0, void 0, function* () { if (subscription.subscriber.id === this.id) { try { const timestamp = log.info('[start] startSubscribing', yield (0, util_1.createLogPayload)({ operationName: 'onPublicationSubscribed', channel: this.channel, }), { subscription }); const options = (_a = this._subscribing[subscription.publication.id]) === null || _a === void 0 ? void 0 : _a.options; if (options) { subscription.preferredEncoding = options.preferredEncodingId; } yield this._subscribingAgent.startSubscribing(subscription); this.onPublicationSubscribed.emit({ subscription, stream: subscription.stream, }); this.onSubscriptionListChanged.emit(); log.elapsed(timestamp, '[end] startSubscribing', yield (0, util_1.createLogPayload)({ operationName: 'onPublicationSubscribed', channel: this.channel, }), { subscription, }); } catch (error) { this._onStreamSubscribeFailed.emit({ error, subscription }); throw error; } } if (subscription.publication.publisher.id === this.id) { if (subscription.subscriber.id === this.id) { throw (0, util_1.createError)({ operationName: 'localPerson._handleOnStreamSubscribe', path: log.prefix, info: Object.assign(Object.assign({}, errors_1.errors.internal), { detail: 'can not subscribe own Publication' }), channel: this.channel, context: this.context, }); } const timestamp = log.info('[start] startPublishing', yield (0, util_1.createLogPayload)({ operationName: 'onPublicationSubscribed', channel: this.channel, }), { subscription }); yield this._publishingAgent.startPublishing(subscription).catch((e) => { log.error('[failed] startPublishing', e, { subscription }); throw e; }); log.elapsed(timestamp, '[end] startPublishing', yield (0, util_1.createLogPayload)({ operationName: 'onPublicationSubscribed', channel: this.channel, }), { subscription }); } }); } /**@throws {@link SkyWayError} */ _handleOnPublicationUnsubscribe(subscription) { return __awaiter(this, void 0, void 0, function* () { if (subscription.publication.publisher.id === this.id) { const timestamp = log.info('[start] stopPublishing', yield (0, util_1.createLogPayload)({ operationName: 'onPublicationUnsubscribed', channel: this.channel, }), { subscription }); yield this._publishingAgent .stopPublishing(subscription.publication, subscription.subscriber) .catch((e) => { log.error('[failed] stopPublishing', e, { subscription }); throw e; }); log.elapsed(timestamp, '[end] stopPublishing', yield (0, util_1.createLogPayload)({ operationName: 'onPublicationUnsubscribed', channel: this.channel, }), { subscription }); } if (subscription.subscriber.id === this.id) { const timestamp = log.info('[start] stopSubscribing', yield (0, util_1.createLogPayload)({ operationName: 'onPublicationUnsubscribed', channel: this.channel, }), { subscription }); yield this._subscribingAgent.stopSubscribing(subscription).catch((e) => { log.error('[failed] stopSubscribing', { subscription }, e); throw e; }); this.onPublicationUnsubscribed.emit({ subscription }); this.onSubscriptionListChanged.emit(); log.elapsed(timestamp, '[end] stopSubscribing', yield (0, util_1.createLogPayload)({ operationName: 'onPublicationUnsubscribed', channel: this.channel, }), { subscription }); } }); } /**@throws {@link SkyWayError} */ publish(stream, options = {}) { var _a, _b, _c, _d, _e, _f; return __awaiter(this, void 0, void 0, function* () { const timestamp = log.info('[start] publish', yield (0, util_1.createLogPayload)({ operationName: 'localPerson.publish', channel: this.channel, }), { options }); if (this.state !== 'joined') { throw (0, util_1.createError)({ operationName: 'localPerson.publish', info: errors_1.errors.localPersonNotJoinedChannel, path: log.prefix, channel: this.channel, context: this.context, }); } if (stream.published) { throw (0, util_1.createError)({ operationName: 'localPerson.publish', channel: this.channel, context: this.context, info: errors_1.errors.alreadyPublishedStream, path: log.prefix, }); } stream.published = true; if (options.codecCapabilities) { options.codecCapabilities = options.codecCapabilities.filter((c) => c !== undefined && c !== null); } options.type = (_a = options.type) !== null && _a !== void 0 ? _a : 'p2p'; const init = { metadata: options.metadata, publisher: this.id, channel: this.channel.id, contentType: stream.contentType, codecCapabilities: (_b = options.codecCapabilities) !== null && _b !== void 0 ? _b : [], isEnabled: options.isEnabled, type: options.type, }; if (stream.contentType === 'video' && ((_c = init.codecCapabilities) === null || _c === void 0 ? void 0 : _c.length) === 0) { init.codecCapabilities = [{ mimeType: 'video/vp8' }]; } if (options.encodings && options.encodings.length > 0) { init.encodings = (0, publication_1.normalizeEncodings)((0, publication_1.sortEncodingParameters)(options.encodings)); } const published = yield this._requestQueue.push(() => this.channel._publish(init).catch((e) => { throw (0, util_1.createError)({ operationName: 'localPerson.publish', context: this.context, channel: this.channel, info: e.info, path: log.prefix, error: e, }); })); // publication作成時にpublication.state=isEnabledとなり、その後isEnabledに合わせてpublicationのenableStream/disableStreamを呼び出してもsetIsEnabled/setEnabledが実行されない。 // そのままではpublication.stateとstreamで状態の乖離が発生する場合があるため、ここで直接実行し一致させておく。 if (stream.contentType === 'data') { stream.setIsEnabled(published.isEnabled); } else { yield stream.setEnabled(published.isEnabled); } const publication = this.channel._addPublication(published); publication._setStream(stream); if ((_d = init.codecCapabilities) === null || _d === void 0 ? void 0 : _d.length) { publication.setCodecCapabilities(init.codecCapabilities); } if ((_e = init.encodings) === null || _e === void 0 ? void 0 : _e.length) { publication.setEncodings(init.encodings); } yield this._handleOnStreamPublish(publication); log.elapsed(timestamp, '[end] publish', yield (0, util_1.createLogPayload)({ operationName: 'localPerson.publish', channel: this.channel, }), { publication }); // dataの場合はMediaDeviceがないので送信処理をしない if (['video', 'audio'].includes(publication.contentType) && this._analytics && !this._analytics.isClosed()) { // 再送時に他の処理をブロックしないためにawaitしない void this._analytics.client.sendMediaDeviceReport({ publicationId: publication.id, mediaDeviceName: publication.deviceName, mediaDeviceTrigger: 'publish', updatedAt: Date.now(), }); const encodings = (_f = init.encodings) !== null && _f !== void 0 ? _f : []; void this._analytics.client.sendPublicationUpdateEncodingsReport({ publicationId: publication.id, encodings: encodings, updatedAt: Date.now(), }); } return publication; }); } _handleOnStreamPublish(publication) { return __awaiter(this, void 0, void 0, function* () { log.info('onStreamPublished', yield (0, util_1.createLogPayload)({ operationName: 'onStreamPublished', channel: this.channel, })); this.onStreamPublished.emit({ publication }); this.onPublicationListChanged.emit(); }); } /**@throws {@link SkyWayError} */ unpublish(target) { return __awaiter(this, void 0, void 0, function* () { const timestamp = log.info('[start] unpublish', yield (0, util_1.createLogPayload)({ operationName: 'localPerson.unpublish', channel: this.channel, })); const publicationId = typeof target === 'string' ? target : target.id; if (this.state !== 'joined') { throw (0, util_1.createError)({ operationName: 'localPerson.unpublish', info: errors_1.errors.localPersonNotJoinedChannel, path: log.prefix, context: this.context, channel: this.channel, }); } const publication = this.channel._getPublication(publicationId); if (!publication) { throw (0, util_1.createError)({ operationName: 'localPerson.unpublish', info: errors_1.errors.publicationNotExist, path: log.prefix, context: this.context, channel: this.channel, payload: { publicationId }, }); } if (publication.stream) { publication.stream._unpublished(); } yield this._requestQueue.push(() => this.channel._unpublish(publicationId)); publication._setStream(undefined); publication.subscriptions .map((s) => s.subscriber) .forEach((s) => { if ((0, remoteMember_1.isRemoteMember)(s)) { this._publishingAgent.stopPublishing(publication, s).catch((e) => { log.error('[failed] stopPublishing', e, { publication }); }); } }); yield this._handleOnStreamUnpublished(publication); log.elapsed(timestamp, '[end] unpublish', yield (0, util_1.createLogPayload)({ operationName: 'localPerson.unpublish', channel: this.channel, }), { publication }); }); } _handleOnStreamUnpublished(publication) { return __awaiter(this, void 0, void 0, function* () { log.info('onStreamUnpublished', yield (0, util_1.createLogPayload)({ operationName: 'onStreamUnpublished', channel: this.channel, })); this.onStreamUnpublished.emit({ publication }); this.onPublicationListChanged.emit(); }); } /**@throws {@link SkyWayError} */ subscribe(target, options = {}) { return __awaiter(this, void 0, void 0, function* () { const timestamp = log.info('[start] subscribe', yield (0, util_1.createLogPayload)({ operationName: 'localPerson.subscribe', channel: this.channel, }), { target }); const publicationId = typeof target === 'string' ? target : target.id; if (this.state !== 'joined') { throw (0, util_1.createError)({ operationName: 'localPerson.subscribe', info: errors_1.errors.localPersonNotJoinedChannel, path: log.prefix, context: this.context, channel: this.channel, payload: { target }, }); } const publication = this.channel._getPublication(publicationId); if (publication === undefined) { throw (0, util_1.createError)({ operationName: 'localPerson.subscribe', info: errors_1.errors.publicationNotExist, path: log.prefix, context: this.context, channel: this.channel, payload: publication, }); } this._validatePublicationForSubscribe(publication); this._subscribing[publication.id] = { options, processing: true, }; const subscribing = this._subscribing[publication.id]; try { const subscriptionDto = yield this._requestQueue.push(() => this.channel._subscribe(this.id, publicationId)); log.elapsed(timestamp, '[elapsed] subscribe / subscriptionDto received', { subscriptionDto, }); const subscription = this.channel._addSubscription(subscriptionDto); if (!subscription.stream) { yield Promise.race([ new Promise((r, f) => { this.onPublicationSubscribed .watch(({ subscription }) => subscription.publication.id === publicationId, this.context.config.rtcApi.timeout) .then(r) .catch((e) => __awaiter(this, void 0, void 0, function* () { if (subscribing.processing) { f((0, util_1.createError)({ operationName: 'localPerson.subscribe', info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'failed to subscribe publication. maybe publisher already left room' }), path: log.prefix, context: this.context, channel: this.channel, payload: { subscription, publication }, error: e, })); } })); }), new Promise((r, f) => { this.channel.onMemberLeft .watch((e) => e.member.id === publication.publisher.id, this.context.config.rtcApi.timeout + 1000) .then(() => { if (subscribing.processing) { f((0, util_1.createError)({ operationName: 'localPerson.subscribe', info: Object.assign(Object.assign({}, errors_1.errors.internal), { detail: 'failed to subscribe publication. publisher already left room' }), path: log.prefix, context: this.context, channel: this.channel, payload: { subscription, publication }, })); } }) .catch(r); }), new Promise((r, f) => { this._onStreamSubscribeFailed .watch((e) => e.subscription.publication.id === publication.id, this.context.config.rtcApi.timeout + 1000) .then((e) => { var _a, _b; if (subscribing.processing) { const info = (_b = (_a = e === null || e === void 0 ? void 0 : e.error) === null || _a === void 0 ? void 0 : _a.info) !== null && _b !== void 0 ? _b : Object.assign(Object.assign({}, errors_1.errors.internal), { detail: 'subscribe _onStreamSubscribeFailed' }); f((0, util_1.createError)({ operationName: 'localPerson.subscribe', info, path: log.prefix, context: this.context, channel: this.channel, error: e.error, payload: { subscription, publication }, })); } }) .catch(r); }), ]); } subscribing.processing = false; log.elapsed(timestamp, '[end] subscribe', yield (0, util_1.createLogPayload)({ operationName: 'localPerson.subscribe', channel: this.channel, }), { subscription, publication }); return { subscription: subscription, stream: subscription.stream, }; } catch (error) { subscribing.processing = false; // 対象のPublicationがすでにUnPublishされている時に失敗しうる log.warn('[failed] subscribe', error, { publication }); throw error; } }); } /**@throws {@link SkyWayError} */ _validatePublicationForSubscribe(publication) { if (publication.publisher.id === this.id) { throw (0, util_1.createError)({ operationName: 'localPerson._validatePublicationForSubscribe', info: errors_1.errors.publicationNotExist, path: log.prefix, context: this.context, channel: this.channel, payload: { publication }, }); } if (publication.publisher instanceof member_2.UnknownMemberImpl) { throw (0, util_1.createError)({ operationName: 'localPerson._validatePublicationForSubscribe', info: errors_1.errors.unknownMemberType, path: log.prefix, context: this.context, channel: this.channel, payload: { publication }, }); } if (this.subscriptions.find((s) => s.publication.id === publication.id)) { throw (0, util_1.createError)({ operationName: 'localPerson._validatePublicationForSubscribe', info: errors_1.errors.alreadySubscribedPublication, path: log.prefix, context: this.context, channel: this.channel, payload: { publication }, }); } } /**@throws {@link SkyWayError} */ unsubscribe(target) { return __awaiter(this, void 0, void 0, function* () { const timestamp = log.info('[start] unsubscribe', yield (0, util_1.createLogPayload)({ operationName: 'localPerson.unsubscribe', channel: this.channel, })); const subscriptionId = typeof target === 'string' ? target : target.id; if (this.state !== 'joined') { throw (0, util_1.createError)({ operationName: 'localPerson.unsubscribe', info: errors_1.errors.localPersonNotJoinedChannel, path: log.prefix, context: this.context, channel: this.channel, }); } const subscription = this.subscriptions.find((s) => s.id === subscriptionId); if (!subscription) { throw (0, util_1.createError)({ operationName: 'localPerson.unsubscribe', info: errors_1.errors.subscriptionNotExist, path: log.prefix, context: this.context, channel: this.channel, payload: { subscriptionId }, }); } delete this._subscribing[subscription.publication.id]; yield this._requestQueue.push(() => this.channel._unsubscribe(subscriptionId)); log.elapsed(timestamp, '[end] unsubscribe', yield (0, util_1.createLogPayload)({ operationName: 'localPerson.unsubscribe', channel: this.channel, }), { subscription }); }); } _getRemoteMemberConnections() { const connections = this.channel.members .filter(remoteMember_1.isRemoteMember) .map((m) => m._getConnection(this.id)); const active = connections.filter((c) => (c === null || c === void 0 ? void 0 : c.closed) === false); return active; } /** * リソース解放 * - メッセージサービスとのセッション * - メディア通信 * - イベントリスナー * - TTL更新 */ dispose() { if (this._disposed) { return; } this._disposed = true; log.debug('disposed', this.toJSON()); clearInterval(this.ttlInterval); if (this._signaling) { this._signaling.close(); } if (this._analytics) { this._analytics.close(); } this._getRemoteMemberConnections().forEach((c) => { c.close({ reason: 'localPerson disposed' }); }); this._onDisposed.emit(); this._events.dispose(); this._disposer.dispose(); } } exports.LocalPersonImpl = LocalPersonImpl; //# sourceMappingURL=index.js.map