12
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SkywayのJavaScript SDKの中身を読んでみよう

Posted at

こんにちは pco2699というものです。
普段は自分のはてなブログに記事を書くのですが、たまにはQiitaに書いてみます。

この記事は Skyway Advent Calendar 2018の15日目の記事です。

やること

他のSkywayアドベントカレンダーだと、「つくってみた」系が多かったので
違う観点でSDKのコードリーディングとかしてみるのはどうだろう、と思いました。

以前、Skywayつかったことあるんですか結構SDKの中身を見てデバッグすることが多かったので
役立つと思います。

願わくば、将来的にはプルリクとか出したい...!!!

早速見てみよう

リポジトリはこちらです。
https://github.com/skyway/skyway-js-sdk

全部見るのは、流石に無理なので
今回はとりあえず、接続を確立する部分を見ていきたいと思います。

個人的には、WebRTCのAPIを呼びまくってる部分を見てみて
**「ほぉ...WebRTCってやっぱよくわからんな...、Skyway偉大...!」**という結論を出すまで読んでいきたいと思います。

リポジトリの構成

ざっとリポジトリは以下のような構成になってます。

.
├── CHANGELOG.md
├── examples // サンプルが入っている
├── karma.conf.js
├── LICENSE
├── package.json
├── package-lock.json
├── README.md
├── scripts // CI用のコード
├── src // SDKのソースコード
├── tests // テストコード
└── webpack.config.js

まずリポジトリ構成をみて面白かったのは

  • CI用のスクリプトがかなり手厚い
  • CI用のスクリプトもJavaScriptで書かれてる

という点でしょうか。
自分だったらCI用のスクリプトはbashとかで書いてしまうので、のっぴきならぬJS好きか、なにか環境制約があったのか、気になるところです。

package.jsonもみてみよう

せっかくなんで、依存しているライブラリがまとまっているpackage.jsonも見てみます。

  "devDependencies": {
    "babel-loader": "^7.1.4",
    "babel-plugin-espower": "^2.4.0",
    "babel-plugin-istanbul": "^4.1.6",
    "babel-preset-es2015": "^6.24.1",
    "del-cli": "^1.1.0",
    "eslint": "^5.0.0",
    "eslint-config-prettier": "^2.9.0",
    "eslint-plugin-prettier": "^2.4.0",
    "inject-loader": "^4.0.1",
    "karma": "^2.0.0",
    "karma-chrome-launcher": "^2.2.0",
    "karma-coverage": "^1.1.2",
    "karma-mocha": "^1.3.0",
    "karma-mocha-reporter": "^2.2.5",
    "karma-sourcemap-loader": "^0.3.7",
    "karma-webpack": "^3.0.0",
    "mocha": "^5.2.0",
    "power-assert": "^1.4.4",
    "prettier": "^1.9.2",
    "sinon": "^6.0.1",
    "webpack": "^4.12.1",
    "webpack-cli": "^3.0.8"
  },
  "dependencies": {
    "detect-browser": "^4.2.0",
    "enum": "git+https://github.com/eastandwest/enum.git#react-native",
    "events": "^3.0.0",
    "js-binarypack": "0.0.9",
    "object-sizeof": "^1.3.0",
    "query-string": "^6.4.0",
    "sdp-interop": "^0.1.13",
    "sdp-transform": "^2.7.0",
    "socket.io-client": "^2.2.0"
  }

気になるパッケージはinject-loaderpower-assertですね。
調べてみたところinject-loaderはモックテスト用にimportするモジュールを差し替えられるライブラリ。
power-assertはテスト時のassertがFailしたときの内容をきれいに出してくれるライブラリ、ということで両方ともテスト用のライブラリでした。

さすがに、企業のSDKだけあってテストはしっかりしてそうです。(当たり前

いよいよ srcの中へ

というわけでソースコードが格納されているsrcの中を見ていきます。

.
├── peer
│   ├── connection.js
│   ├── dataConnection.js
│   ├── mediaConnection.js
│   ├── meshRoom.js
│   ├── negotiator.js
│   ├── room.js
│   ├── sfuRoom.js
│   └── socket.js
├── peer.js
└── shared
    ├── config.js
    ├── logger.js
    ├── sdpUtil.js
    └── util.js

srcの中はこんな感じになってます。

早速、最初のスタートポイントであるpeer.jsを見てみましょう。

peer.js
import EventEmitter from 'events';
import Enum from 'enum';

// (省略)
const PeerEvents = new Enum([
  'open',
  'error',
  'call',
  'connection',
  'expiresin',
  'close',
  'disconnected',
]);

/**
 * Class that manages all p2p connections and rooms.
 * This class contains socket.io message handlers.
 * @extends EventEmitter
 */
class Peer extends EventEmitter {
  /**
   * Create new Peer instance. This is called by user application.
   * @param {string} [id] - User's peerId.
   * (省略)
   */
  constructor(id, options) {
    super();

    this.connections = {};
    this.rooms = {};

    // messages received before connection is ready
    this._queuedMessages = {};

    if (id && id.constructor === Object) {
      options = id;
      id = undefined;
    } else if (id) {
      id = id.toString();
    }

    const defaultOptions = {
      debug: logger.LOG_LEVELS.NONE,
      secure: true,
      token: util.randomToken(),
      config: config.defaultConfig,
      // (省略)
    };

    this.options = Object.assign({}, defaultOptions, options);

    logger.setLogLevel(this.options.debug);
    // (省略)
  }

ES2015のクラス構文でめちゃくちゃキレイに書かれてますね。読みやすい!(素人の感想)
ここから、どこを見れば、最初の接続部分の処理になるのか簡単にわかりそうです。

ここで、APIのリファレンスを見てみて、どのAPIを呼べば接続開始するのか見てみましょう。
SkywayのAPIのリファレンス

Peer - JS SDK API Reference - Google Chrome 2019-03-30 00.12.28.png

peerのcallメソッドで、指定したpeerとメディアチャンネルをつくるようです。
APIの通り実際にコードの中でもMediaConnectionオブジェクトが作られていますね。
そして、作られたMediaConnectionオブジェクトのstartConnection()メソッドを呼んで
接続を開始しているようです。

peer.js
  /**
   * Creates new MediaConnection.
   * (省略)
   */
  call(peerId, stream, options = {}) {
    if (!this._checkOpenStatus()) {
      return;
    }

    options.originator = true;
    options.stream = stream;
    options.pcConfig = this._pcConfig;
    const mc = new MediaConnection(peerId, options);
    mc.startConnection();
    logger.log('MediaConnection created in call method');
    this._addConnection(peerId, mc);
    return mc;
  }

次はMediaConnectionのstartConnection()部のソースコードを見てましょう。

MediaConnection.js
  /**
   * Start connection via negotiator and handle queued messages.
   * @return {Promise<void>} Promise that resolves when starting is done.
   */
  async startConnection() {
    if (!this._options.originator) {
      return;
    }

    await this._negotiator.startConnection({
      type: 'media',
      stream: this.localStream,
      // 省略
    });

    this._pcAvailable = true;
    this._handleQueuedMessages();
  }

ふむふむ、どうやらnegotiatorstartConnectionを呼んでるみたいです。
次はnegotiatator内を見てみましょう。ここらへんからはAPIのリファレンスには説明があまり載ってこない世界になってきます。

negotiator.js
  async startConnection(options = {}) {
    this._pc = this._createPeerConnection(options.pcConfig);
    // 省略

    if (this._type === 'media') {
      if (options.stream) {
        if (this._isAddTrackAvailable && !this._isForceUseStreamMethods) {
          options.stream.getTracks().forEach(track => {
            this._pc.addTrack(track, options.stream);
          });
        } else {
          this._pc.addStream(options.stream);
        }
      } else if (this.originator) {
        // This means the peer wants to create offer SDP with `recvonly`
        const offer = await this._makeOfferSdp();
        await this._setLocalDescription(offer);
      }
    }
  /**
   * Create new RTCPeerConnection.
   * (省略)
   */
  _createPeerConnection(pcConfig = {}) {
    logger.log('Creating RTCPeerConnection');

    const browserInfo = util.detectBrowser();

    this._isAddTrackAvailable =
      typeof RTCPeerConnection.prototype.addTrack === 'function';
    this._isOnTrackAvailable = 'ontrack' in RTCPeerConnection.prototype;
    this._isRtpSenderAvailable =
      typeof RTCPeerConnection.prototype.getSenders === 'function';

    // 省略
    return new RTCPeerConnection(pcConfig);
  }

ここでようやくRTCPeerConnectionが出てきました!RTCPeerConnectionはWebRTCのAPIです。
WebRTCのAPIはMozillaのホームページなどに内容の説明が出てきます。
MozillaのWebRTC APIリファレンス

このnegotiatorクラスが主にWebRTC周りのAPIを呼んでいるクラスでした。

ここまで読んで、なんとなく、各ファイルのうっすら責務がわかってきました。
Untitled Diagram.png

まとめ

という感じでざっくり、SkywayのJavaScript SDKの中身で、接続開始のWebRTC APIを叩くところまで呼んでみました。
WebRTCを用いたチャットアプリ、ビデオチャットを作りたい場合には、
WebRTCの実装をうまいこと抽象化してくれているSkywayは非常に便利です。

**「ほぉ...WebRTCってやっぱよくわからんな...、Skyway偉大...!」**という当初の結論を変えず、本記事を締めたいと思います。
どうもありがとうございました。

12
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?