API Docs for:
Show:

File: src/remote_peer.coffee

{Promise} = require('./internal/promise')
Peer = require('./peer').Peer

StreamCollection = require('./internal/stream_collection').StreamCollection
ChannelCollection = require('./internal/channel_collection').ChannelCollection

merge = () ->
  # WARNING: later occurences of the same key will overwrite
  res = {}

  for array in arguments
    for key, value of array
      res[key] = value

  return res


###*
# @module rtc
###
###*
# Represents a remote user of the room
# @class rtc.RemotePeer
# @extends rtc.Peer
#
# @constructor
# @param {rtc.PeerConnection} peer_connection The underlying peer connection
# @param {rtc.SignalingPeer} signaling The signaling connection to the peer
# @param {rtc.LocalPeer} local The local peer
# @param {Object} options The options object as passed to `Room`
###
class exports.RemotePeer extends Peer

  ###*
  # Message received from peer through signaling
  # @event message
  # @param data The payload of the message
  ###

  ###*
  # The remote peer left or signaling closed
  # @event left
  ###

  ###*
  # A new stream is available from the peer
  # @event stream_added
  # @param {String} name Name of the stream
  # @param {Promise -> rtc.Stream} stream Promise of the stream
  ###

  ###*
  # A new data channel is available from the peer
  # @event data_channel_added
  # @param {String} name Name of the channel
  # @param {Promise -> rtc.DataChannel} channel Promise of the channel
  ###

  ###*
  # The connection to the peer supplied by the signaling implementation
  # @property signaling
  # @type rtc.signaling.SignalingPeer
  ###

  constructor: (@peer_connection, @signaling, @local, @options) ->
    # create streams

    @private_streams = {}
    @private_channels = {}

    @stream_collection = new StreamCollection()
    @streams = @stream_collection.streams
    @streams_desc = {}

    @stream_collection.on 'stream_added', (name, stream) =>
      @emit('stream_added', name, stream)

    # channels stuff

    @channel_collection = new ChannelCollection()
    @channels = @channel_collection.channels
    @channels_desc = {}

    @channel_collection.on 'data_channel_added', (name, channel) =>
      @emit('data_channel_added', name, channel)

    # resolve streams and data channels

    @peer_connection.on 'stream_added', (stream) =>
      @stream_collection.resolve(stream)

    @peer_connection.on 'data_channel_ready', (channel) =>
      @channel_collection.resolve(channel)

    # wire up peer connection signaling

    @peer_connection.on 'signaling', (data) =>
      data.streams = @streams_desc
      data.channels = @channels_desc
      @signaling.send('signaling', data)

    @signaling.on 'signaling', (data) =>
      @stream_collection.update(data.streams)
      @channel_collection.setRemote(data.channels)
      @peer_connection.signaling(data)

    @peer_connection.on 'ice_candidate', (candidate) =>
      @signaling.send('ice_candidate', candidate)

    @signaling.on 'ice_candidate', (candidate) =>
      @peer_connection.addIceCandidate(candidate)

    # status handling
 
    @signaling.on 'status_changed', (status) =>
      @emit('status_changed', status)

    # communication

    @signaling.on 'message', (data) =>
      @emit('message', data)

    @signaling.on 'left', () =>
      @peer_connection.close()
      @emit('left')

    # pass on signals

    @peer_connection.on 'connected', () =>

    @peer_connection.on 'closed', () =>
      # TODO

    # we probably want to connect now

    if not @options.auto_connect? or @options.auto_connect
      @connect()


  # documented in Peer
  status: (key) ->
    @signaling.status[key]


  ###*
  # Send a message to the peer through signaling
  # @method message
  # @param data The payload
  # @return {Promise} Promise which is resolved when the data was sent
  ###
  message: (data) ->
    return @signaling.send('message', data)


  ###*
  # Connect to the remote peer to exchange streams and create data channels
  # @method connect
  # @return {Promise} Promise which will resolved when the connection is established
  ###
  connect: () ->
    if not @connect_p?
      # wait for streams

      stream_promises = []

      for name, stream of merge(@local.streams, @private_streams)
        promise = stream.then (stream) ->
          return [name, stream]

        stream_promises.push(promise)

      # TODO: really fail on failed streams?
      @connect_p = Promise.all(stream_promises).then (streams) =>
        # add all streams

        for [name, stream] in streams
          @peer_connection.addStream(stream)
          @streams_desc[name] = stream.id()

        # create data channels

        for name, options of merge(@local.channels, @private_channels)
          @peer_connection.addDataChannel(name, options)
          @channels_desc[name] = options

        @channel_collection.setLocal(@channels_desc)

        # actually connect

        return @peer_connection.connect()

    return @connect_p


  ###*
  # Closes the connection to the peer
  # @method close
  ###
  close: () ->
    @peer_connection.close()
    return


  ###*
  # Get a stream from the peer. Has to be sent by the remote peer to succeed.
  # @method stream
  # @param {String} [name='stream'] Name of the stream
  # @return {Promise -> rtc.Stream} Promise of the stream
  ###
  stream: (name=@DEFAULT_STREAM) ->
    @stream_collection.get(name)


  ###*
  # Add local stream to be sent to this remote peer
  #
  # If you use this method you have to set `auto_connect` to `false` in the options object and call `connect()` manually on all remote peers.
  #
  # @method addStream
  # @param {String} [name='stream'] Name of the stream
  # @param {Promise -> rtc.Stream | rtc.Stream | Object} stream The stream, a promise to the stream or the configuration to create a stream with `rtc.Stream.createStream()`
  # @return {Promise -> rtc.Stream} Promise of the stream which was added
  ###
  addStream: (name, obj) ->
    if not (@options.auto_connect == false)
      return Promise.reject("Unable to add streams directly to remote peers without 'auto_connect' option set to 'false'")

    # helper to actually save stream
    saveStream = (stream_p) =>
      # TODO: collision detection?
      @private_streams[name] = stream_p
      return stream_p

    # name can be omitted ... once
    if typeof name != 'string'
      obj = name
      name = @DEFAULT_STREAM

    if obj?.then?
      # it is a promise
      return saveStream(obj)
    else if obj instanceof Stream
      # it is the actual stream, turn into promise
      return saveStream(Promise.resolve(obj))
    else
      # we assume we can pass it on to create a stream
      stream_p = Stream.createStream(obj)
      return saveStream(stream_p)


  ###*
  # Get a data channel to the remote peer. Has to be added by local and remote side to succeed.
  # @method channel
  # @param {String} [name='data'] Name of the data channel
  # @return {Promise -> rtc.DataChannel} Promise of the data channel
  ###
  channel: (name=@DEFAULT_CHANNEL) ->
    @channel_collection.get(name)


  ###*
  # Add data channel which will be negotiated with this remote peer
  #
  # If you use this method you have to set `auto_connect` to `false` in the options object and call `connect()` manually on all remote peers.
  #
  # @method addDataChannel
  # @param {String} [name='data'] Name of the data channel
  # @param {Object} [desc={ordered: true}] Options passed to `RTCDataChannel.createDataChannel()`
  ###
  addDataChannel: (name, desc) ->
    if not (@options.auto_connect == false)
      return Promise.reject("Unable to add channels directly to remote peers without 'auto_connect' option set to 'false'")

    if typeof name != 'string'
      desc = name
      name = @DEFAULT_CHANNEL

    if not desc?
      # TODO: default handling
      desc = {
        ordered: true
      }

    @private_channels[name] = desc

    return @channel(name)


  ###*
  # Checks whether the peer is the local peer. Returns always `false` on this
  # class.
  # @method isLocal
  # @return {Boolean} Returns `false`
  ###
  isLocal: () ->
    return false