Skip to content

Latest commit

 

History

History
510 lines (425 loc) · 16.4 KB

File metadata and controls

510 lines (425 loc) · 16.4 KB

@webex/plugin-meetings

standard-readme compliant

Meetings plugin for the Cisco Webex JS SDK.

WARNING: This plugin is currently under active development, is not stable, and breaking changes can and will happen!


To create supported meetings and calls using Webex Teams, use @ciscospark/plugin-phone.

Install

npm install --save @webex/plugin-meetings

Usage

This is a plugin for the Cisco Webex JS SDK . Please see our developer portal and the API docs for full details.

Examples

Creating a basic meeting

By conversation ID
let convoId = `ObiwanAnnouncementsConversationUUID`;
return spark.meetings.create(convoId).then((meeting) ==> {...});
By SIP URI
let sipUri = `obiwan@example.com`;
return spark.meetings.create(sipUri).then((meeting) ==> {...});

Joining a meeting

Default
// TODO: list out the specific packages needed to just pull in plugin meetings instead of the full
// ciscospark package?
import ciscospark as spark from 'ciscospark';

let destination = `obiwan@example.com`;
let activeMeeting;

function setMedia(media){
  if (media.type === 'local') {
    document.getElementById('local-media').srcObject = media.stream;
  } else if (media.type === 'remote') {
    document.getElementById('remote-media').srcObject = media.stream;
  }
}

function handleAudioChange(audio){
  // perform some actions after audio has been muted/unmuted
}

function handleVideoChange(video) {
  // perform some action after video has been muted/unmuted
}

return spark.meetings
  .create(destination)
  .then((meeting) => {
    activeMeeting = meeting;

    activeMeeting.on('media:ready', () => setMedia));
    activeMeeting.on('media:audioChanged', handleAudioChange);
    activeMeeting.on('media:videoChanged', handleVideoChange);

    activeMeeting.join({resourceId: <DeviceId>});
  });
Joining a PMR

from above, we build off of the join...

joining your own claimed PMR
...
meeting.join({resourceId: <DeviceId>}).then((res) => {
  // backend services determine you are the owner, so no hostpin, or intent is required
  // now you are in the meeting
});
...
joining someone elses claimed PMR
...
// join as host (in place of them)
meeting.join({resourceId: <DeviceId>, hostPin: <WebexHostPin>, moderator: true}).then((res) => {
  // now you are in the meeting
});
...
// join as attendee
meeting.join({resourceId: <DeviceId>, moderator: false}).then((res) => {
  // if host hasn't started the meeting, now you are in the lobby, else if host has started the meeting, you are in the meeting
});
joining an unclaimed PMR
// join as host automatically
meeting.join({resourceId: <DeviceId>, hostPin: <WebexHostPin>, moderator: true}).then((res) => {
  // now you are in the meeting
});
// join as host with ask user option
// join as attendee
meeting.join({resourceId: <DeviceId>}).then((res) => {
}).catch((err) => {
  if (err.joinIntentRequired) {
    // at this point you can ask the user to join as host or join as guest
    // if join as host, requires a pin
    ...
    // join as host simply makes the join call again with the proper hostpin/moderator parameters
    meeting.join({resourceId: <DeviceId>, hostPin: <WebexHostPin>, moderator: true}).then(() => {
      // you are now in the meeting
    });
    ...
    // join as guest simply makes the call again with moderator parameter
    meeting.join(({resourceId: <DeviceId>, moderator: false})).then(() => {
      // if host hasn't started the meeting, now you are in the lobby, else if host has started the meeting, you are in the meeting
    });
  }
});

Personal Meeting Room

Editing
const link = ...; // a valid pmr link
const pin = ...; // a valid host pin assoicated to the link
// claiming a pmr, and updating the cached values for the stored PMR
spark.meetings.personalMeetingRoom.claim(link, pin).then((pmr) => {
  console.log(pmr); // do something else with the pmr
});
Getting
spark.meetings.personalMeetingRoom.get().then((pmr) => {
  // do some stuff with the pmr values
  console.log(`PMR INFO:
      link-${spark.meetings.personalMeetingRoom.meetingLink}-
      uri-${spark.meetings.personalMeetingRoom.sipUri}-
      tollFree-${spark.meetings.personalMeetingRoom.pmr.callInNumbersInfo.callInTollFreeNumber.number}-
      toll-${spark.meetings.personalMeetingRoom.pmr.callInNumbersInfo.callInTollNumber.number}-
      accessCode-${spark.meetings.personalMeetingRoom.pmr.meetingNumber}
      `);
});

Member

Properties
member.participant ... // Object server participant object, advanced use only
member.id ... // String key for storing
member.name ... // String plain text name
member.isAudioMuted ... // Boolean
member.isVideoMuted ... // Boolean
member.isSelf ... // Boolean is this member YOUR user?
member.isHost ... // Boolean
member.isGuest ... // Boolean
member.isInLobby ... // Boolean
member.isInMeeting ... // Boolean
member.isNotAdmitted ... // Boolean -- waiting to be admitted to the meeting, will also have isInLobby true
member.isContentSharing ... // Boolean
member.status ... // String -- advanced use only
member.isDevice ... // Boolean
member.isUser ... // Boolean
member.associatedUser ... // String -- member.id if isDevice is true
member.isRecording ... // Boolean
// more coming...

Members

You can access the members object on each individual meeting instance, it has some key events to listen to, and maintains what happens for members of a meeting with some key properties.

Properties
meeting.members ...
meeting.membersCollection ... // the members collection, object {id0: member0, ... idN: memberN}
meeting.locusUrl ... // current locusUrl being used
meeting.hostId ... // active host id for the meeting
meeting.selfId ... // active self id for the meeting
meeting.mediaShareContentId ... // active content sharer id for the meeting
Functions
// You can add a guest to the meeting by inviting them, this is proxied by meeting.invite
// use an emailAddress and a boolean value alertIfActive to notify server side (usually true)
meeting.members.addMember(emailAddress, alertIfActive)

// You can admit the guest to the meeting once they are waiting in the lobby, you can do this in bulk, proxied by meeting.admit
// use member ids, can be singular, but has to be put into an array
meeting.members.admitMembers([memberIds])
Events
// members collection updated
meeting.members.on('members:update', (payload) => {
  const delta = payload.delta; // the changes to the members list
  const full = payload.full; // the full members collection
  const updated = delta.updated; // only the updates, includes removals, as they will have updated status and member properties
  const added = delta.added; // added members to the meeting
  Object.keys(full).forEach((key) => {
    const member = full[key];
    console.log(`Member: ... ${member.x}`);
  });
  Object.keys(updated).forEach((key) => {
    const member = updated[key];
    console.log(`Member Updated: ... ${member.x}`);
  });
  Object.keys(added).forEach((key) => {
    const member = added[key];
    console.log(`Member Added: ... ${member.x}`);
  });
});
// content updates
meeting.members.on('members:content:update', (payload) => {
  console.log(`who started sharing: ${payload.activeContentSharingId};`);
  console.log(`who stopped sharing: ${payload.endedContentSharingId};`);
});
// host updates
meeting.members.on('members:host:update', (payload) => {
  console.log(`who started hosting: ${payload.activeHostId};`);
  console.log(`who stopped hosting: ${payload.endedHostId};`);
})
// self updates, not typically used
meeting.members.on('members:self:update', (payload) => {
  console.log(`active self id: ${payload.activeSelfId};`);
  console.log(`ended self Id: ${payload.endedSelfId};`);
})

getStats

Note: accessor methods get are simply helpers, one can always use dot notation to work on the objects in the same way

Builder
// operate with a Transform stream
meeting
  .getStats() // get the stats instance, creates a new one if one does not exist on the meeting object
  .withMedia() // add the media type, to use the media peer connection, can also add on withScreen() for screen peer connection
  .build() // finish up building the getStats instance and get a reference back
  .getMedia() // get the media instance off of getStats
  .withId() // add an ID to the media type of stats, optional
  .withEvents() // trigger 'stats:update' events on media, see 'Events' below, optional
  .withHistory() // collect history with DEFAULT_HISTORY_MAX size (default is 30 minutes, 1800 entries), optional
  .withAggregator() // aggregate data from the stream each time, to have a summary of each stat, optional
  .withInterval() // find the actual values between each registered interval, optional
  .withFilter(meeting.mediaProperties.mediaPeerConnection) // create the Transform stream filter on the media instance to convert the data to
  // a nice human readable format, and parse out relevant stats, defined by DEFAULT_TRANSFORM_REGEX, see nodejs Transform Stream
  .build() // finish up building the media stats instance and get a reference back
  .onData((filtered) => { // do something with each filtered data beyond any of the added options, pass a callback
    console.log(filtered);
  });
// OR
// for raw get stats without a filter, getting just a Readable stream back, see nodejs Readable Stream
// not for use with events, history, aggregator, interval, will result in undefined behavior
meeting
  .getStats() // get the stats instance, creates a new one if one does not exist on the meeting object
  .withMedia() // add the media type, to use the media peer connection, can also add on withScreen() for screen peer connection
  .build() // finish up building the getStats instance and get a reference back
  .getMedia() // get the media instance off of getStats
  .withStream(meeting.mediaProperties.mediaPeerConnection); // create the Readable stream on the media instance to be able to set up a data pipe
  // or a simple listener for the stats reports coming back, to do your own stats filtering/analysis
meeting.getStats().getMedia().getStream().on('data', (data) => {
  // do something with the raw getStats report
});
// afterwards for each you may just call to get a stats instance, without having to create it's configuration using with()
meeting.getStats()...
Options
// functionally equivalent to the builder
// pass in with a standard options object which pieces you want to create on the stats instance
meeting.getStats({ // create the stats reference
  screen: { // with screen option
    id: true, // with an id on the screen stats
    history: true, // with history
    interval: true, // with interval
    filter: meeting.mediaProperties.sharePeerConnection, // with filter
    events: true // with events
  },
  media: { // can add media or exclude
    ...
  }
});
meeting.getStats().getScreen().onData((filtered) => {
  console.log(filtered);
});
// afterwards, to have the same instance you may just call
meeting.getStats()...
Adding Configurations On The Fly
// you can always add additional configurations later on based on events, or some timing, using the builder above
// say you created the object above with the options method, and you decided you wanted to add an aggregator to it
meeting
.getStats()
.getScreen()
.withAggregator();
// and now you have an aggregator instance to work with on that screen stats object
meeting.stats.screen.aggregator...
Aggregator
// note: using the aggregator is probably not necessary unless the WebRTC spec changes, or browsers change behavior and no longer automatically
// summarize their data
meeting
  .getStats() // get the stats instance on the meeting
  .getMedia() // get the media peer connection related stats, can also do getScreen()
  .getAggregator() // get the aggregator instance on that media stats
  .getSummary(); // get the last found summary object
Events
meeting
  .getStats() // get the stats instance on the meeting
  .getMedia() // get the media peer connection related stats, can also do getScreen()
  .getEvents() // get the events instance on that media stats
  .on('stats:update', (stats) => {
    // listen to the stats update event that comes through here
    // do something with the stats event, see Events below
  });
History
meeting
  .getStats() // get the stats instance on the meeting
  .getMedia() // get the media peer connection related stats, can also do getScreen()
  .getHistory() // get the history instance on that media stats
  .getHistory(); // get the actual array of history stats objects
Interval
meeting
  .getStats() // get the stats instance on the meeting
  .getMedia() // get the media peer connection related stats, can also do getScreen()
  .getInterval() // get the interval instance on that media stats
  .getInterval(); // get the actual interval object that was last saved
Putting It All Together
// create
meeting
  .getStats()
  .withMedia()
  .build();
meeting
  .getStats()
  .getMedia()
  .withId()
  .withFilter(meeting.mediaProperties.mediaPeerConnection)
  .withEvents()
  .withHistory()
  .withInterval()
  .withAggregator()
  .build();
meeting
  .getStats()
  .withScreen()
  .build();
meeting
  .getStats()
  .getScreen()
  .withId()
  .withStream(meeting.mediaProperties.sharePeerConnection);
// do stuff
meeting
  .getStats()
  .getMedia()
  .getStream()
  .on('data', (data) => {
    console.log(data);
  });
meeting
  .getStats()
  .getMedia()
  .onData((filtered) => {
    console.log(filtered);
  });
meeting
  .getStats()
  .getMedia()
  .getEvents()
  .on('stats:update', (stats) => {
    console.log(stats);
  });

Events

There are several events submitted by this package that you can subscribe to.

Event Name Description
media:ready Fired when remote or local media has been acquired
media:audioChanged Fired whenever local audio streams are muted and unmuted
media:videoChanged Fired whenever local video streams are muted and unmuted
media:stopped Fired when remote or local media has been torn down
stats:update Fired when a getStats key event occurs (first audio/video bytes received/sent)
--- ---
media:ready has the following format
{
  type, // local or remote
    stream; // the MediaStream
}

media:audioChanged and media:videoChanged has the following format

{
  type, // local or error
    status, // string representation of state
    muted, // boolean true or false for muted or not
    result; // the promise error or resolution value
}

media:stopped has the following format

{
  type; // local or remote
}

stats:update has the following format

{
  type, // local or remote
    key, // which key it matched, i.e., rtpOutAudio, etc, defined by DEFAULT_EVENT_VIDEO|AUDIO_RECEIVE|SEND_KEYS
    data, // the data at the point that the event was fired
    stat, // the accessor for the stat itself, i.e, bytesSent, bytesReceived
    id, // the id specified when creating the events instance
    kind; // audio or video
}

Development

To use webpack-dev-server to load this package, run npm run samples:serve.

Files placed in the packages/node_modules/samples/browser-plugin-meetings folder will be served statically.

Files in the src folder will be compiled, bundled, and served as a static asset at bundle.js inside that directory.

Maintainers

This package is maintained by Cisco Webex for Developers.

Contribute

Pull requests welcome. Please see CONTRIBUTING.md for more details.

License

© 2016-2018 Cisco and/or its affiliates. All Rights Reserved.